1. 程式人生 > >AOP+ASM+外掛化總結--實現基於註解的埋點和統計-- 程式碼篇之:Transform

AOP+ASM+外掛化總結--實現基於註解的埋點和統計-- 程式碼篇之:Transform

git地址+ASM文件
總結一下:基本都是制式的程式碼,包括遍歷那一塊等等,其他方法需要的型別和返回值也都在註釋裡了。

import com.android.build.api.transform.*
import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.pipeline.TransformManager
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.io.FileUtils
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter

import static org.objectweb.asm.ClassReader.EXPAND_FRAMES

class MyPlugin extends Transform implements Plugin<Project> {

    void apply(Project project) {
        def android = project.extensions.getByType(AppExtension)
        android.registerTransform(this)

        /**
         * 對外註冊api:
         android.registerTransform(new XTransform());
         android.registerTransform(new XTransform(), dependencies)
         內部註冊api
         TransformManager.addTransform();
         */
    }

//    Transform中的核心方法,
//    inputs中是傳過來的輸入流,其中有兩種格式,一種是jar包格式一種是目錄格式。
//    outputProvider 獲取到輸出目錄,最後將修改的檔案複製到輸出目錄,這一步必須做不然編譯會報錯

    @Override
    void transform(Context context, Collection<TransformInput> inputs,
                   Collection<TransformInput> referencedInputs,
                   TransformOutputProvider outputProvider,
                   boolean isIncremental) throws IOException, TransformException, InterruptedException {
        println '//===============TracePlugin visit start===============//'
        //刪除之前的輸出
        if (outputProvider != null)
            outputProvider.deleteAll()
        //遍歷input
        inputs.each { TransformInput input ->
            // 遍歷資料夾
            input.directoryInputs.each {
                DirectoryInput directoryInput ->
//                    File dir = directoryInput.file
                    if (directoryInput.file.isDirectory()) {
                        //遍歷資料夾
                        directoryInput.file.eachFileRecurse {
                            File file ->
                                // ...對目錄進行插入位元組碼
                                def name = file.name
                                if (name.endsWith(".class") && !name.startsWith("R\$") &&
                                        !"R.class".equals(name) && !"BuildConfig.class".equals(name)) {
                                    println(name + "    is chang......")
                                    //這就要用到ASM插入了
                                    ClassReader classReader = new ClassReader(file.bytes)
                                    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                                    def className = name.split(".class")[0]
                                    ClassVisitor cv = new TraceVisitor(className, classWriter)
                                    classReader.accept(cv, EXPAND_FRAMES)
                                    byte[] code = classWriter.toByteArray()
                                    FileOutputStream fos = new FileOutputStream(
                                            file.parentFile.absolutePath + File.separator + name)
                                    fos.write(code)
                                    fos.close()
                                    //插入結束
                                }
                        }
                    }
                    // 獲取output目錄 處理完輸入檔案之後,要把輸出給下一個任務
                    def dest = outputProvider.getContentLocation(directoryInput.name,
                            directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
                    // 將input的目錄複製到output指定目錄
                    FileUtils.copyDirectory(directoryInput.file, dest)
            }
            ////遍歷jar檔案 對jar不操作,但是要輸出到out路徑
            input.jarInputs.each {
                JarInput jarInput ->
                    if (jarInput.file.getAbsolutePath().endsWith(".jar")) {
                        // ...對jar進行插入位元組碼
                    }

                    // 重新命名輸出檔案(同目錄copyFile會衝突)
                    def jarName = jarInput.name
                    println("jar = " + jarInput.file.getAbsolutePath())
                    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)
            }
        }

    }

/**
 * 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.
 */

    @Override
    String getName() {
        return this.getClass().simpleName
    }

//轉換過程中需要資源流的範圍,在轉換過程中不會被消耗,轉換結束後, 會將資源流放回資源池去
    @Override
    Set<? super QualifiedContent.Scope> getReferencedScopes() {
        return super.getReferencedScopes()
    }

    //轉換輸出型別,預設是getInputTypes()
    //需要處理的資料型別,有兩種列舉型別
    //CLASSES和RESOURCES,CLASSES代表處理的java的class檔案,RESOURCES代表要處理java的資源
    @Override
    Set<QualifiedContent.ContentType> getOutputTypes() {
        return TransformManager.CONTENT_CLASS
    }
    /**
     * 定義了你要處理的型別
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * Returns the scope(s) of the Transform. This indicates which scopes the transform consumes.
     * Transform的作用域,主要是三大類:SCOPE_FULL_PROJECT SCOPE_FULL_WITH_IR_FOR_DEXING  SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS
     * //    EXTERNAL_LIBRARIES        只有外部庫
     * //    PROJECT                       只有專案內容
     * //    PROJECT_LOCAL_DEPS            只有專案的本地依賴(本地jar)
     * //    PROVIDED_ONLY                 只提供本地或遠端依賴項
     * //    SUB_PROJECTS              只有子專案。
     * //    SUB_PROJECTS_LOCAL_DEPS   只有子專案的本地依賴項(本地jar)。
     * //    TESTED_CODE                   由當前變數(包括依賴項)測試的程式碼

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

    /**
     * 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.
     * 指明當前Transform是否支援增量編譯
     */
    @Override
    boolean isIncremental() {
        return false
    }
}