AOP+ASM+外掛化總結--實現基於註解的埋點和統計-- 程式碼篇之:Transform
阿新 • • 發佈:2019-01-26
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 } }