編譯插樁-Transform

自定義好gradle之後,需要實現遍歷class文件的邏輯,拿到所有的.class文件之後才能做之後的工作, 然而這部分的功能主要靠transform API 實現

Transform

簡單來講,Transform 是Gradle在編譯項目時的一個task,在.class 文件轉換成.dex的流程中會執行這些task, 很明顯,.class文件轉換爲.dex之前就是我們操作.class文件的最佳也是唯一一個機會了

一、 創建一個Transform

Transform是一個抽象類,自定義一個類繼承Transform

public class CustomLogTransform extends Transform {
    
}

1.1 主要的抽象方法說明

  • String getName()

設置自定義的Transform 對應的task名稱, Gradle 在編譯的時候,會將這個名稱顯示eg: Task:app:transformClassesWithCustomLogTransformForDebug

  • Set<QualifiedContent.ContentType> getInputTypes()

在項目中會有各種各樣格式的文件,通過getInputType可以設置,CustomLogTransform接收的文件類型,此方法返回的類型是Set<QualifiedContent.ContentType>集合

ContentType:

  • CLASSES 只檢索.class文件
  • RESOURCES: 檢索Java標準資源文件
  • Set<? super QualifiedContent.Scope> getScopes()

自定義 Transform 檢索的範圍,具體有以下幾種取值:

  • PROJECT: 只有項目內容
  • SUB_PROJECTS: 只有子項目
  • EXTERNAL_LIBRARIES: 只有外部庫
  • TESTED_CODE: 由當前變量(包括依賴項)測試的代碼
  • PROVIDED_ONLY: 只提供本地或遠程依賴項
  • SUB_PROJECTS_LOCAL_DEPS: 只有子項目的本地依賴項(本地jar)
  • PROJECT_LOCAL_DEPS: 只有項目的本地依賴(本地jar)
  • boolean isIncremental()

是否支持增量編譯 不需要直接返回false,一般都返回false

  • void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException

自定義時最重要的方法,在這個方法中可以獲取兩個數據的流向

  • inputs: inputs 中傳過來的輸入流,其中有兩種格式,jat包格式,directory(目錄格式)
  • outputProvider:outputProvider 獲取到輸出目錄,最後將修改的文件複製到輸出目錄,這一步必須做,否則編譯會報錯。

1.2 獲取項目中所有的.class 的文件

  • 實現自定義的Transform 的transform方法
public class CustomLogTransform extends Transform {

    /**
     * Returns the unique name of the transform.
     *
     * <p>This is associated with the type of work that the transform does. It does not have to be
     * unique per variant.
     * 設置自定義的Transform 對應的task名稱, Gradle 在編譯的時候,會將這個名稱顯示
     * 在控制檯上
     * eg: Task:app:transformClassesWithXXXForDebug
     */
    @Override
    String getName() {
        return "CustomLogTransform"
    }

    /**
     * Returns the type(s) of data that is consumed by the Transform. This may be more than
     * one type.
     * 在項目中會有各種各樣格式的文件,通過getInputType可以設置
     * CustomLogTransform接收的文件類型,此方法返回的類型是Set<QualifiedContent.ContentType>集合
     * ContentType:
     * CLASSES: 只檢索.class文件
     * RESOURCES: 檢索Java標準資源文件
     * <strong>This must be of type {@link QualifiedContent.DefaultContentType}</strong>
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * Returns the scope(s) of the Transform. This indicates which scopes the transform consumes.
     * 這個方法規定自定義 Transform 檢索的範圍,具體有以下幾種取值:
     * PROJECT: 只有項目內容
     * SUB_PROJECTS: 只有子項目
     * EXTERNAL_LIBRARIES:只有外部庫
     * TESTED_CODE: 由當前變量(包括依賴項)測試的代碼
     * PROVIDED_ONLY: 只提供本地或遠程依賴項
     * SUB_PROJECTS_LOCAL_DEPS: 只有子項目的本地依賴項(本地jar)
     * PROJECT_LOCAL_DEPS: 只有項目的本地依賴(本地jar)
     */
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.PROJECT_ONLY
    }

    /**
     * Returns whether the Transform can perform incremental work.
     *
     * <p>If it does, then the TransformInput may contain a list of changed/removed/added files, unless
     * something else triggers a non incremental run.
     * 是否支持增量編譯 不需要直接返回false
     */
    @Override
    boolean isIncremental() {
        return false
    }

    /**
     * 自定義時最重要的方法,在這個方法中可以獲取兩個數據的流向
     * inputs: inputs 中傳過來的輸入流,其中有兩種格式,jat包格式,directory(目錄格式)
     * outputProvider:outputProvider 獲取到輸出目錄,最後將修改的文件複製到輸出目錄,這一步必須做,否則編譯會報錯。
     * @param transformInvocation
     * @throws TransformException* @throws InterruptedException* @throws IOException
     */
    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        // 拿到所有的class文件
        Collection<TransformInput> transformInputs = transformInvocation.inputs
        TransformOutputProvider outputProvider = transformInvocation.outputProvider
        if (outputProvider != null) {
            outputProvider.deleteAll()
        }
        transformInputs.each { TransformInput transformInput ->
            transformInput.jarInputs.each { JarInput jarInput ->
                File file = jarInput.file
                System.out.println("find jar input: " + file.name)
                def dest = outputProvider.getContentLocation(jarInput.name,
                        jarInput.contentTypes,
                        jarInput.scopes, Format.JAR)
                FileUtils.copyFile(file, dest)
            }
            // // 遍歷directoryInputs(文件夾中的class文件) directoryInputs代表着以源碼方式參與項目編譯的所有目錄結構及其目錄下的源碼文件
            //            // 比如我們手寫的類以及R.class、BuildConfig.class以及MainActivity.class等
            transformInput.directoryInputs.each { DirectoryInput directoryInput ->
                File dir = directoryInput.file
                if (dir) {
                    dir.traverse(type: FileType.FILES, nameFilter: ~/.*\.class/) { File file ->
                        System.out.println("find class: " + file.name)
                        // 對Class 文件進行讀取與解析
                        ClassReader classReader = new ClassReader(file.bytes)
                        // class 文件寫入
                        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                        // 訪問class 文件相應的內容、解析某一個結構就會通知到ClassVisitor的相應方法
                        CustomClassVisitor classVisitor = new CustomClassVisitor(classWriter)
                        // 依次調用ClassVisitor 接口的各個方法
                        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
                        // 將最終修改的字節碼以byte數組形式返回
                        byte[] bytes = classWriter.toByteArray()
                        // 通過文件流寫入方式覆蓋原先的內容,實現class文件的改寫
                        FileOutputStream fileOutputStream = new FileOutputStream(file.path)
                        fileOutputStream.write(bytes)
                        fileOutputStream.close()
                    }
                    // 處理完輸入之後吧輸出傳給下一個文件
                    def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
                    FileUtils.copyDirectory(directoryInput.file, dest)
                }
            }
        }

    }
}

  • 將自定義的Transform註冊到自定義的Gradle插件中去
public class CustomLogPlugin implements Plugin<Project> {
    void apply(Project project) {
        System.out.println("======CustomLogPlugin===")
        def android = project.extensions.getByType(AppExtension)
        println '----------registering AutoTrackTransform -------'
        // 註冊transform
        CustomLogTransform transform = new CustomLogTransform()
        android.registerTransform(transform)
    }
}

二、進行編譯

build 後就可以了

> Configure project :app
======CustomLogPlugin===
----------registering AutoTrackTransform -------

並且會在打印所有的class 文件

 Task :app:transformClassesWithCustomLogTransformForDebug

find class: BuildConfig.class
find class: MainActivity.class

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