Javassist 小记

Javassist 小记

13 January 2018

近期,由于需要看一些框架的源码。所以,需要了解一下 Javassistandroid gradle 插件方面的使用。




Transform


Transform API 是在 AGP 1.5 之后才出现的。用于:在打包成为 dex 之前,操作 class 文件。

但是,其实在 AGP 1.5 之前,是没有 Transform API 的。操作 class 文件,是局限于两个 gradle task 内。 名为 preDexdex

AGP 1.5 之后,伴随着 Transform API 的出现。preDexdex 变为了 TransfromClassesWithDexForDebug 或者 TransfromClassesWithDexForRelease

目前,AGPandroid 项目打包的时候。在 混淆任务Jar 合成 以及 Instant Run 上都用了 Transform API




Transform 也是 Task


com.android.build.gradle.internal.pipeline.TransformManager 找到这个方法。


/**
 * Adds a Transform.
 *
 * <p>This makes the current transform consumes whatever Streams are currently available and
 * creates new ones for the transform output.
 *
 * <p>his also creates a {@link com.android.build.gradle.internal.pipeline.TransformTask} to run the transform and wire it up with the
 * dependencies of the consumed streams.
 *
 * @param taskFactory the task factory
 * @param scope the current scope
 * @param transform the transform to add
 * @param callback a callback that is run when the task is actually configured
 * @param <T> the type of the transform
 * @return {@code Optional<AndroidTask<Transform>>} containing the AndroidTask for the given
 *     transform task if it was able to create it
 */
@NonNull
public <T extends Transform> Optional<AndroidTask<TransformTask>> addTransform(  
        @NonNull TaskFactory taskFactory,
        @NonNull TransformVariantScope scope,
        @NonNull T transform,
        @Nullable TransformTask.ConfigActionCallback<T> callback) {

    ...

    transforms.add(transform);

    // create the task...
    AndroidTask<TransformTask> task =
            taskRegistry.create(
                    taskFactory,
                    new TransformTask.ConfigAction<>(
                            scope.getFullVariantName(),
                            taskName,
                            transform,
                            inputStreams,
                            referencedStreams,
                            outputStream,
                            recorder,
                            callback));

    for (TransformStream s : inputStreams) {
        task.dependsOn(taskFactory, s.getDependencies());
    }
    for (TransformStream s : referencedStreams) {
        task.dependsOn(taskFactory, s.getDependencies());
    }

    return Optional.ofNullable(task);
}


最终都会将 Transform 包装成一个 AndroidTask




Transform 工作流程


javassist_1


如图。
本次 Transforminput 是上一个 Transformouput
本次 Transfromouput 是下一个 Transforminput




Transform 使用


getName: 定义了 Transformname。比如 return 'lifeTransform'。那么得到的 name 就会为 transformClassesWithLifeTransformForDebugtransformClassesWithLifeTransformForRelease


getInputTypes: 规定了 Transform 处理的数据类型。

CLASSES: javacclass 文件。
RESOURCES: Java 资源。
DEX: class dx 后,编译成的 dex 文件。


getScopes: 规定了 Transform 作用域。

Scope.PROJECT: 只有 项目内容。
Scope.SUB PROJECTS: 只有 子项目。
Scope.EXTERNAL LIBRARIES: 只有 外部库。
Scope.TESTED CODE: 只有 测试代码。
Scope.PROVIDED ONLY: 只有 提供本地或远程依赖项。
Scope.PROJECT LOCAL DEPS: 只有 当前项目的本地依赖,例如 jar, aar


isIncremental: 是否支持 增量编译。


/**
 * CaMnter
 * */

class LifeTransform extends Transform {

    private Project project

    LifeTransform(Project project) {
        this.project = project
    }

    /**
     * transformClassesWith ${name} ForDebug
     * transformClassesWith ${name} ForRelease
     *
     * @return Transform name
     */
    @Override
    String getName() {
        return 'lifeTransform'
    }

    /**
     * TransformManager.CONTENT_CLASS
     * TransformManager.CONTENT_JARS
     * TransformManager.CONTENT_RESOURCES
     * TransformManager.CONTENT_NATIVE_LIBS
     * TransformManager.CONTENT_DEX
     * TransformManager.DATA_BINDING_ARTIFACT
     *
     * @return
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * TransformManager.PROJECT_ONLY
     * TransformManager.SCOPE_FULL_PROJECT
     * TransformManager.SCOPE_FULL_WITH_IR_FOR_DEXING
     * TransformManager.SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS
     *
     * Scope.PROJECT
     * 只有 项目内容
     *
     * Scope.SUB_PROJECTS
     * 只有 子项目
     *
     * Scope.EXTERNAL_LIBRARIES
     * 只有 外部库
     *
     * Scope.TESTED_CODE
     * 只有 测试代码
     *
     * Scope.PROVIDED_ONLY
     * 只有 提供本地或远程依赖项
     *
     * @return
     */
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    /**
     * 是否支持 增量编译
     *
     * @return
     */
    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation)
            throws TransformException, InterruptedException, IOException {
        // doing something
    }
}




Javassist 生成一个 class


/**
 * CaMnter
 **/

class LifeJavassistExtension {

    String sign = "Default sign value"

}


/**
 * CaMnter
 **/

class LifeJavassistPlugin implements Plugin<Project> {

    static String LIFE_JAVASSIST_EXTENSION = 'lifeJavassistExtension'
    AppExtension android

    @Override
    void apply(Project project) {
        if (!project.plugins.hasPlugin(AppPlugin.class)) return

        android = project.extensions.findByType(AppExtension.class)
        final LifeTransform lifeTransform = new LifeTransform(project)
        android.registerTransform(lifeTransform)

        project.extensions.create(LIFE_JAVASSIST_EXTENSION, LifeJavassistExtension)
        android.applicationVariants.all {
            final ApkVariantData variantData = (it as ApplicationVariantImpl).variantData
            final VariantScope scope = variantData.scope

            final LifeJavassistExtension lifeJavassistExtension = project.extensions.getByName(
                    LIFE_JAVASSIST_EXTENSION)

            // 创建生成 LifeJavassistExtension 的 Task
            def taskName = scope.getTaskName('lifeJavassistExtensionTask')
            final Task task = project.task(taskName)
            task.doLast {
                createLifeJavassistExtension(scope, lifeJavassistExtension)
            }

            // 寻找生成 BuildConfig 的 Task
            def buildConfigTaskName = scope.getGenerateBuildConfigTask().name
            final Task buildConfigTask = project.tasks.getByName(buildConfigTaskName)

            // 生成任务滞后于 buildConfigTask
            if (buildConfigTask) {
                task.dependsOn buildConfigTask
                buildConfigTask.finalizedBy task
            }
        }
    }

    static def createLifeJavassistExtension(VariantScope scope,
            LifeJavassistExtension lifeJavassistExtension) {
        def classContent =
                """
/**
 * Automatically generated file by javassist
 */
package com.camnter.newlife;

/**
 * CaMnter
 */

public class LifeJavassistExtension {

    public static final String JAVASSIST_TAG = "Class created by javassist";
    public static final String JAVASSIST_USER_SIGN = "${lifeJavassistExtension.sign}";

}
"""
        final File buildConfigOutputDir = scope.getBuildConfigSourceOutputDir()
        def javaFile = new File(buildConfigOutputDir, 'LifeJavassistExtension.java')
        javaFile.write(classContent, 'UTF-8')
    }
}


javassist_2


BuildConfig.java 的目录下,生成一个 LifeJavassistExtension.java




Javassist 修改 class


目标:修改 JavassistActivity.class 内的代码。


/**
 * CaMnter
 * */

class JavassistActivityInject extends BaseInject {

    private static final String TAG = JavassistActivityInject.simpleName

    JavassistActivityInject(Project project) {
        super(project)
    }

    /**
     * 目录
     *
     * @param directoryInput directoryInput
     */
    @Override
    def inject(DirectoryInput directoryInput) {
        if (!checkoutAppExtension()) return
        // 注入
        def dirPath = directoryInput.file.absolutePath
        final ClassPool classPool = ClassPool.getDefault()
        /**
         * 当前文件夹路径加入 ClassPool
         * 否则,找不到类
         * */
        classPool.appendClassPath(dirPath)
        /**
         * BaseExtension 中 取 android.jar
         *
         * 加入 android.jar 否则
         * 找不到 android 相关类
         * */
        classPool.appendClassPath(android.bootClasspath[0].toString())
        /**
         * JavassistActivity 有这些 import
         *
         * import android.widget.TextView;
         * import android.widget.Toast;
         * import com.camnter.newlife.R;
         * */
        classPool.importPackage('android.widget.TextView')
        classPool.importPackage('android.widget.Toast')
        classPool.importPackage('com.camnter.newlife.R')

        def dirFile = directoryInput.file
        if (dirFile.isDirectory()) {
            dirFile.eachFileRecurse {
                def filePath = it.absolutePath
                if (it.name.equals('JavassistActivity.class')) {
                    println "[${TAG}]   JavassistActivity.class was found   [filePath] = ${filePath}"
                    final CtClass applicationClass = classPool.getCtClass(
                            'com.camnter.newlife.ui.activity.javassist.JavassistActivity')
                    println "[${TAG}]   [JavassistActivity CtClass] = ${applicationClass.toString()}"

                    /**
                     * 解冻
                     *
                     * 如果一个 CtClass 对象通过 writeFile(),toClass() 或者 toBytecode()
                     * 转换成了 class 文件
                     *
                     * 那么 javassist 会冻结这个 CtClass
                     * 后面就不能继续修改这个 CtClass
                     *
                     * 为了警告
                     * 开发者不要修改已经被 JVM 加载的 class 文件,因为 JVM 不允许重新加载一个类
                     * */
                    if (applicationClass.isFrozen()) {
                        applicationClass.defrost()
                    }

                    final CtMethod onCreate = applicationClass.getDeclaredMethod('initViews')
                    println '[${TAG}]   JavassistActivity#initViews was found'

                    def injectContent =
                            """
final TextView textView = (TextView) this.findView(R.id.text);  
final String showText = "Javassist success";  
textView.setText(showText);  
Toast.makeText(this, showText, Toast.LENGTH_LONG).show();  
"""
                    onCreate.insertBefore(injectContent)
                    applicationClass.writeFile(dirPath)
                    // 释放
                    applicationClass.detach()
                }
            }
        }
    }

    /**
     * jar
     *
     * @param jarInput jarInput
     */
    @Override
    def inject(JarInput jarInput) {
        def md5Path = DigestUtils.md5Hex(jarInput.file.absolutePath)
        def md5Name = jarInput.name
        if (md5Name.endsWith(".jar")) {
            md5Name = md5Name.substring(0, md5Name.length() - 4)
            md5Name = md5Name + md5Path
        }
        return md5Name
    }
}


/**
 * CaMnter
 * */

class LifeTransform extends Transform {

    private Project project

    LifeTransform(Project project) {
        this.project = project
    }

    /**
     * transformClassesWith ${name} ForDebug
     * transformClassesWith ${name} ForRelease
     *
     * @return Transform name
     */
    @Override
    String getName() {
        return 'lifeTransform'
    }

    /**
     * TransformManager.CONTENT_CLASS
     * TransformManager.CONTENT_JARS
     * TransformManager.CONTENT_RESOURCES
     * TransformManager.CONTENT_NATIVE_LIBS
     * TransformManager.CONTENT_DEX
     * TransformManager.DATA_BINDING_ARTIFACT
     *
     * @return
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * TransformManager.PROJECT_ONLY
     * TransformManager.SCOPE_FULL_PROJECT
     * TransformManager.SCOPE_FULL_WITH_IR_FOR_DEXING
     * TransformManager.SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS
     *
     * Scope.PROJECT
     * 只有 项目内容
     *
     * Scope.SUB_PROJECTS
     * 只有 子项目
     *
     * Scope.EXTERNAL_LIBRARIES
     * 只有 外部库
     *
     * Scope.TESTED_CODE
     * 只有 测试代码
     *
     * Scope.PROVIDED_ONLY
     * 只有 提供本地或远程依赖项
     *
     * @return
     */
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    /**
     * 是否支持 增量编译
     *
     * @return
     */
    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation)
            throws TransformException, InterruptedException, IOException {
        def input = transformInvocation.inputs
        if (input == null) return
        JavassistActivityInject mainApplicationInject = new JavassistActivityInject(project)
        input.each {
            // 文件夹
            it.directoryInputs.each {
                // 注入代码
                mainApplicationInject.inject(it)
                def output = transformInvocation.outputProvider.getContentLocation(it.name,
                        it.contentTypes,
                        it.scopes, Format.DIRECTORY)
                // input 目录复制到 output 目录
                FileUtils.copyDirectory(it.file, output)
            }

            // jar 文件
            it.jarInputs.each {
                def md5Name = mainApplicationInject.inject(it)
                def output = transformInvocation.outputProvider.getContentLocation(md5Name,
                        it.contentTypes,
                        it.scopes, Format.JAR)
                FileUtils.copyFile(it.file, output)
            }
        }
    }
}


javassist_3


如图。
成功在 JavassistActivity.class 插入的代码。