Shadow does the core work of plug-in technology by inserting an intermediate layer into the plug-in through bytecode editing. So, it’s worth sharing with those who are new to bytecode editing the introductory posture of studying this technology.

Introduction to the Build Process

The official Android build process provides an API called TransForm, as described here. This API allows third-party plug-ins to edit classes before they are converted to dex. Shadow wrote such a third party API, is in the plug-in build. Add the apply of gradle plugin: ‘com. Tencent. Shadow. The plugin.

Regarding transforms, note that multiple transforms are allowed in a build. But the build system does not guarantee the order of these transforms. It’s multiple transforms who executes first and who executes later, and you can’t assume that when you’re designing a Transform. Shadow’s Transform changes the parent class of some classes, or the name of some classes. If any other Transform tries to find a class by type or name, its execution before Shadow’s Transform will not be the same as its execution after Shadow’s Transform. In this case, it is recommended to modify Shadow’s Transform and write both transforms in the same Plugin so that the sequence can be controlled within the Plugin.

Plug-in after application of the Shadow of the Plugin, during the build process is invoked to com. Tencent. Shadow. Core. Gradle. ShadowPlugin# apply. It finds the target class through this configuration in projects/ SDK /core/gradle-plugin/build.gradle.

gradlePlugin {
    plugins {
        shadow {
            id = "com.tencent.shadow.plugin"
            implementationClass = "com.tencent.shadow.core.gradle.ShadowPlugin"}}}Copy the code

In Apply, we use this code to register our Transform program with the build system.

plugin.extension.registerTransform(ShadowTransform(...) )Copy the code

Performs a Transform tasks, such as transformClassesWithShadowTransformForDebug. This task is called ShadowTransform object of the superclass method com. Tencent. Shadow. Core. Transform_kit. ClassTransform# transform, we transform into the real entrance.

The incremental build detection mechanism for Gradle tasks only checks whether the input and output of the task have changed. This is obviously reasonable in ordinary circumstances. The input is the same, the output is there, so you don’t have to redo the task. However, in Shadow development, the Transform task was also part of our source code. Therefore, under this default mechanism, the plug-in’s original bytecode is the input, and the plug-in’s edited bytecode is the output. If only the logic of Transform is changed, for example, the logic of class A is renamed to B, and the logic of class A is renamed to C. Since the input remains the same and the output remains, the updated Transform logic will not execute. So in com. Tencent. Shadow. Core. Transform_kit. ClassTransform# getSecondaryFiles, we will Transform the program itself to do in order to Transform with the input of the program. This allows the Transform task to determine that the input has changed due to the change in the Transform program itself and re-execute the Transform task. This allowed me to edit the Transform source code and run it with one click.

Look at the plug-in bytecode before and after the Transform

During the development, we made sample-plugin into an AAR library, and then packaged it into the normally installed sample-normal-app and the plug-in sample-plugin-app respectively, in order to conveniently check the bytecode differences before and after the application of Transform.

Double-click an APK file in Android Studio (1 in the image below) and select the dex file in details (2 in the image below) to display the classes in dex. Click the button indicated by 3 in the figure below to hide classes that are not really packaged in the current dex. Then select a class (figure 4 below), right-click, and select Show Bytecode.

For example, to view the bytecode of a normally installed APK, see the following:

.class public Lcom/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityReCreate;
.super Landroid/app/Activity;
.source "TestActivityReCreate.java"

# direct methods.method public constructor <init>()V .registers 1 .line 31 invoke-direct {p0}, Landroid/app/Activity; -><init>()Vreturn-void
.end method
Copy the code

Look again at the bytecode of the same class in plug-in APK as follows:

.class public Lcom/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityReCreate;
.super Lcom/tencent/shadow/core/runtime/ShadowActivity;
.source "TestActivityReCreate.java"

# direct methods.method public constructor <init>()V .registers 1 .line 31 invoke-direct {p0}, Lcom/tencent/shadow/core/runtime/ShadowActivity; -><init>()Vreturn-void
.end method
Copy the code

You can see that in line 2, dot super has changed, which means that the parent of this class has changed. The method invoked by invoke-Direct in the following method has also changed.

Modify Transform program

Activity such as the previous example involving system replace ShadowActivity, is in a class com. Tencent. Shadow. Core. The transform. Specific. ActivityTransform. You can modify the source code of this class directly, and then re-run sample-host to see the effect.

Shadow for all bytecode modification logic on the com. Tencent. Shadow. Core. The transform. The specific package.

Shadow’s transform-kit is the generic code Shadow uses for bytecode editing, and should be able to be used directly for any bytecode editing on Android. Such as accessing the business directly and implementing SpecificTransform for AOP programming.

The design of the transform – kit

Transform-kit’s design is certainly untested. I hope you can improve it after using open source construction.

It has mainly solved these problems at present.

  1. ClassTransformWork out how to input your App’s own classes and dependent JAR packages into memory for editing, and then output them to a file after editing.
  2. JavassistTransformFigure out how to bringClassTransformAssociated with Javassist.
  3. AbstractTransformThe abstract process responsible for organizing the Transform, such as setup followed by fire, is fixed here.
  4. AbstractTransformManager,SpecificTransform,TransformStepTo organize parallel and unrelated transforms. eachSpecificTransformBy multipleTransformStepComposition. Configure Step and then execute it. This is designed so that each Transform works sequentially, and each Transform works with all classes. Instead of each class going through all the transforms sequentially, Shadow’s earliest code made this mistake. Each class goes through all the transforms, and the next class references another class that either has already handled it or hasn’t handled it.
  5. AndroidClassPoolBuilderComplete how to import the Android SDK classes into Javassist for use.

To share so much, I believe some people know they can learn by documents refer to Javassist Javassist usage, then look at com. Tencent. Shadow. Core. The transform. The specific package in existing code, You’ll be able to thoroughly learn how to extend Shadow’s functionality, and you’ll be able to use Shadow’s transform-Kit for AOP programming in other applications.