Gradle 進階:動態編譯技術

前面兩篇文章介紹了 Gradle自定義插件以及擴展配置的用法。 今天我們來看一下一個具體的應用場景,動態編譯。我們將嘗試在編譯期間修改class文件。

初識Transform

Android Gradle 工具在 1.5.0 版本後提供了 Transfrom API, 允許第三方 Plugin 在打包 dex 文件之前的編譯過程中 操作 .class 文件。目前 jarMerge、proguard、multi-dex、Instant-Run 都已經換成 Transform 實現。

如下圖:

具體怎麼操作呢?我們在自定義的Gradle插件中先創建一個自己的Transform。 重寫的transform方法處就是處理class文件的時機。

public class MyTransform extends Transform {
  Project project

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

  @Override
  void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
    //todo
  }

  @Override
  String getName() {
    return "MyTransform"
  }

  @Override
  Set<QualifiedContent.ContentType> getInputTypes() {
    return TransformManager.CONTENT_CLASS
  }

  @Override
  Set<QualifiedContent.Scope> getScopes() {
    return TransformManager.SCOPE_FULL_PROJECT
  }

  @Override
  boolean isIncremental() {
    return false
  }
}

當然我們希望做點什麼,例如修改我們類中的某個方法,或加入一行log。(尤其當你需要修改一些你沒有源碼修改權限的第三方Jar包時) 我們需要藉助另一個工具,javassist。

javassist

先配置一下插件的gradle

dependencies {
  compile gradleApi()
  compile localGroovy()
  compile 'com.android.tools.build:gradle:3.2.1'
  compile 'com.android.tools.build:transform-api:1.5.0'
  compile 'javassist:javassist:3.12.1.GA'
  compile 'commons-io:commons-io:2.5'
}

然後創建一個插入的代碼類MyInjects,該類的作用就是往MainActivity中插入一句log-->"Hello world!"

public class MyInjects {

private static ClassPool pool = ClassPool.getDefault()
private static String injectStr = "System.out.println(\"Hello world!\" ); "

public static void injectDir(String path) {
    pool.appendClassPath(path)
    File dir = new File(path)

    if (dir.isDirectory()) {
        dir.eachFileRecurse { File file ->

            String filePath = file.absolutePath

            if (filePath.endsWith("MainActivity.class")) {

                String className = "com.example.huhu.kotlindemo.ac.MainActivity"
                CtClass c = pool.getCtClass(className)

                if (c.isFrozen()) {
                    c.defrost()
                }

                CtConstructor[] cts = c.getDeclaredConstructors()
                if (cts == null || cts.length == 0) {
                    CtConstructor constructor = new CtConstructor(new CtClass[0], c)
                    constructor.insertBeforeBody(injectStr)
                    c.addConstructor(constructor)
                } else {
                    cts[0].insertBeforeBody(injectStr)
                }

                c.writeFile(path)
                c.detach()
            }

         }
      }
   }
}

接着在Transform中調用,transform的inputs包含了兩個部分 jar 包和目錄:

@Override
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
    inputs.each { TransformInput input ->

        input.directoryInputs.each { DirectoryInput directoryInput ->
            MyInjects.injectDir(directoryInput.file.absolutePath)

            def dest = outputProvider.getContentLocation(directoryInput.name,
                    directoryInput.contentTypes, directoryInput.scopes,
                    Format.DIRECTORY)

            FileUtils.copyDirectory(directoryInput.file, dest)
        }

        input.jarInputs.each { JarInput jarInput ->

            def jarName = jarInput.name
            def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
            if (jarName.endsWith(".jar")) {
                jarName = jarName.substring(0, jarName.length() - 4)
            }

            def dest = outputProvider.getContentLocation(jarName + md5Name,
                    jarInput.contentTypes, jarInput.scopes, Format.JAR)

            FileUtils.copyFile(jarInput.file, dest)
        }
    }
}

最後,我們看一下,在Gradle Plugin中如何註冊Transform:

class DemoPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        def android = project.extensions.getByType(AppExtension)
        android.registerTransform(new MyTransform(project))
    }
}

然後,我們運行App以後,就可以發現:

 I/System.out: Hello world!

這只是一種簡單的用法,大家可以查閱javassist更多的用法去做更多事情。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章