First, you need to be familiar with the APK packaging process, bytecode knowledge, Gradle, to understand the following content.

1.Transform key methods

@Override
String getName() {
    return "try-catch transform"} / / CLASSES to handle the compiled bytecode, may be a jar package may also be a directory / / RESOURCES processing standard Java RESOURCES @ Override the Set < QualifiedContent. ContentType >getInputTypes() {
    returnTransformmanager.content_class} // scope of Transform, such as current project, subproject, local dependencies of subproject, etc. super QualifiedContent.Scope>getScopes() {
    returnSCOPE_FULL_PROJECT} // Whether incremental compilation is supported @override BooleanisIncremental() {
    return false@override void Transform (TransformInvocation TransformInvocation) throws TransformException, InterruptedException, IOException {super.transform(transformInvocation)} Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, Boolean isIncremental) // However, this method has been deprecated, using transform(transformInvocation), // Public void Transform (@nonNULL TransformInvocation TransformInvocation) throws TransformException, InterruptedException, IOException { // Just delegate to old method,for code that uses the old API.
        //noinspection deprecation
        transform(transformInvocation.getContext(), transformInvocation.getInputs(),
                transformInvocation.getReferencedInputs(),
                transformInvocation.getOutputProvider(),
                transformInvocation.isIncremental());
    }
Copy the code

2. Gradle debugging

  1. Create a Plugin debug task! [image-20200609173703523](/Users/jackie/Library/Application Support/typora-user-images/image-20200609173703523.png)

  2. Terminal input:

    ./gradlew assembleDebug -Dorg.gradle.daemon=false -Dorg.gradle.debug=true

    And then the terminal screen will stay here! [image-20200609173445941](/Users/jackie/Library/Application Support/typora-user-images/image-20200609173445941.png)

  3. Click the Debug button on the AS to set a breakpoint and start debugging

    Reference: www.jianshu.com/p/ea3e00c5e… Gralde file debugging and more (fucknmb.com/2017/07/05/…

  4. A breakpoint cannot enter the Transform method of a Transform

3.Android Extension

  1. Define the Extension first

  2. Create the Extension in plugin, and then create a task to enter the values we set

    //1. TryCatchPlugin
    
    project.extensions.create("tryCatchInfo",TryCatchExtension)
    println("============create TryCatchInfo Extension")
    project.afterEvaluate {
        println("============afterEvaluate=========")
        project.task("tryCatchTask".type} //2. TryCatchTask extends DefaultTask{TryCatchExtension Use the no-parameter constructor here, and then assign the value to tryCatchExtension in the constructorTryCatchTask(){
            tryCatchExtension = project.tryCatchInfo
            println("====TryCatchTask======Constructor======"+tryCatchExtension.toString())
        }
    
        @TaskAction
        void run(){
            println("====Task run====")
            println(tryCatchExtension.toString())
        }
    }
    
    
    tryCatchInfo {
        pathName = "com.jackie.testlib.MyClass"
        methodName = "testCrash"
        exceptionName = "java.lang.Exception"
        returnValue = 10
    }
    
    Copy the code
  3. use

    apply plugin: 'com.jackie.trycatch'
    
    classpath 'com. Jackie. Trycatch: trycatchplugin: 1.0'
    
    tryCatchInfo {
        pathName = "com.jackie.testlib.MyClass"
        methodName = "testCrash"
        exceptionName = "java.lang.Exception"
        returnValue = 10}./gradlew tryCatchTask  > Task :app:tryCatchTask ====Task run==== TryCatchExtension.groovy{pathName=com.jackie.testlib.MyClassmethodName=testCrashexceptionName=java.lang.Exception, returnValue='10'}
    
    Copy the code

4.ASM

There is no trick to learning ASM, just look at the API, use some plug-ins to look at the bytecode, practice a lot, and then you can get started, and then you can achieve mastery.

You can learn about ASM in conjunction with the ASM learning notes from the previous article

ASM designs two types, one is based on the Tree API, one is based on the Visitor API(Visitor pattern).

The Tree API reads the class structure into memory and builds a Tree structure. Then, when processing elements such as Method and Field, the Tree locates an element in the Tree structure, performs operations, and writes the operations to a new class file.

The Visitor API separates the logic that reads and writes classes by interface, typically through a ClassReader that reads the class bytecode, and then through a ClassVisitor interface that reads the class bytecode. Each detail of the bytecode is passed in order through the interface to the ClassVisitor (you’ll find multiple visitXXXX interfaces in the ClassVisitor), just as the ClassReader takes the ClassVisitor through each instruction of the class bytecode.

The above two kinds of parsing the file structure of the way in many dealing with structured data are common, general background to choose the appropriate scheme depends on demand, and our demand is such, for a certain purpose, looking for a hook point in the class files, to bytecode modification, this background, the way we choose the Visitor API is more appropriate.

In the following code, the Visitor API reads the contents of a class and saves them to another file

private void copy(String inputPath, String outputPath) {
    try {
        FileInputStream is = new FileInputStream(inputPath);
        ClassReader cr = new ClassReader(is);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        cr.accept(cw, 0);
        FileOutputStream fos = new FileOutputStream(outputPath);
        / / ClassWriter toByteArray, will transfer to ClassReader ClassWriter bytecode export, write a new file.
        fos.write(cw.toByteArray());
        fos.close();
    } catch(IOException e) { e.printStackTrace(); }}Copy the code

First, we read a class file from a ClassReader file, and then we define a ClassWriter, which we can look at the source code for, which is basically a ClassVisitor implementation, Write ClassReader data to a byte stream, which is triggered by ClassReader accept.

public void accept(ClassVisitor classVisitor, Attribute[] attributePrototypes, int 		  parsingOptions) {
    
    // Read the bytecode information of the current class
    int accessFlags = this.readUnsignedShort(currentOffset);
    String thisClass = this.readClass(currentOffset + 2, charBuffer);
    String superClass = this.readClass(currentOffset + 4, charBuffer);
    String[] interfaces = new String[this.readUnsignedShort(currentOffset + 6)];

    
    
    //classVisitor is the ClassWriter passed in by the accept method. VisitXXX is responsible for storing the bytecode information each time
    classVisitor.visit(this.readInt(this.cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces);
    
    /** leaves out a lot of visit logic */

    //visit Attribute
    while(attributes ! =null) {
        Attribute nextAttribute = attributes.nextAttribute;
        attributes.nextAttribute = null;
        classVisitor.visitAttribute(attributes);
        attributes = nextAttribute;
    }

    /** leaves out a lot of visit logic */

    classVisitor.visitEnd();
}
Copy the code

5. Problems encountered by ASM

  1. Problem with trycatch return value

  2. GetCommonSuperClass (), which finds the common parent of both classes.

    The source code uses the classloader, but the classloader used by the compiler does not load the code in the Android project, so we need to create a custom classloader that takes all the jars and classes received in the Transform mentioned above, And android.jar are added to the custom ClassLoader. (Some of the problems with this method are hinted at in the above method comments.)

    However, if we just replace the Classloader in getCommonSuperClass, there is still a further hole. We can look at the implementation of getCommonSuperClass. It loads a Class via class.forname and then looks for its parent Class. However, classes in Android.jar cannot be loaded casually. Android.jar is a compile-time dependency for android projects. The runtime uses the Android machine’s own Android.jar. And all of the android.jar methods, including constructors, are empty implementations with only one line of code.

    throw new RuntimeException("Stub!" );Copy the code

    When a class is loaded, its static field is fired, and if a static variable is initialized at declaration time and only a RuntimeException is initialized, an exception is thrown.

    So, we can’t get the parent class this way, but can we get the parent class without loading the class? The parent class is also an item in the bytecode of a class, so we can query the parent class from the bytecode. Use ClassWriter, ClassReader, etc.

  3. Mainactivity. Java generates the mainactivity. class file, which can not be generated by using javac mainactivity. Java. In the app/build/intermediates/javac directory looking for below. The class files

  4. Check whether the code is added successfully, apK compilation process will have intermediate products, generate JAR package, can be viewed here, do not need to decompile to view, /Users/jackie/Desktop/WorkPlace/AsmDemo/app/build/intermediates/transforms/TimeTransform

  5. AsmDemo contains multiple instances github.com/ljzyljc/Asm… Github.com/dikeboy/Dhj…

    1. Statistical method time, can cross method
    2. Add a try-catch
    3. OnClick adds burials
    4. Replace the new Thread with the CustomThread
    5. I wanted to implement global string encryption, but it is too expensive to continue the research, and I am also struggling with the lack of support, so I will go into further research in the next project, I have already done some research, with bytecode foundation/Gradle, etc., next time will be very fast.
  6. Imitate Hunter framework to write basic processing process, transform and other processes can not debug, must remember to log, new understanding of a lot of exceptions, constructor problems, method call parameter problems, ASM plugin show difference use, a class to reference another class, other classes need to be bytecode, Then the class can be generated. R file related cannot generate bytecode.

  7. Insert code encountered problems, remember android and other beginning of the package, kotlin and other libraries, can not insert code, remember to ignore.

  8. Sometimes the print in log cannot be found when searching for classes. It may be that the printed log is too long, so it is necessary to conduct a general fuzzy search and look it over.

  9. These pitfalls are inevitable in real project development, so real code practice is essential to get started.