一、Gradle Transform到底是什么
Gradle Transform是Android官方提供給開發者在項目構建階段(.class -> .dex轉換期間)用來修改.class文件的一套標準API,即把輸入的.class文件轉變成目標字節碼文件,目前比較經典的應用是字節碼插樁、代碼注入等。
二、Gradle Transform怎么用
1、在build.gradle文件中添加Gradle插件依賴
buildscript { dependencies { classpath 'com.android.tools.build:gradle:x.x.x' // 其他插件依賴 }}apply plugin: 'com.android.application' // 或其他所需插件
2、編寫Transform類
編寫Transform類,在其中實現對Java字節碼進行的修改。Transform類需要繼承自Transform接口并實現其兩個方法:
public class MyTransform extends Transform { @Override public String getName() { return "myTransform"; } @Override public Set getInputTypes() { return TransformManager.CONTENT_CLASS; } @Override public Set super QualifiedContent.Scope> getScopes() { return TransformManager.SCOPE_FULL_PROJECT; } @Override public boolean isIncremental() { return false; } @Override public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { // 實現Transform邏輯 }}
3、配置Transform
在build.gradle文件中配置Transform,將其作為編譯期間的一個任務執行:
android { ... // 配置Transform transformClassesWithMyTransformForDebug { // 可選配置項,如下所示 // enable false // ignoreWarnings true // enableTransformForJar false }}dependencies { ...}
完成以上步驟后,Gradle會在編譯期間執行Transform對Java字節碼進行修改,從而實現各種自動生成代碼、字節碼增強等功能。
三、Transform編寫模板
1、無增量編譯
AspectJTransform.groovy代碼如下:
class AspectJTransform extends Transform { final String NAME = "JokerwanTransform" @Override String getName() { return NAME } @Override SetgetInputTypes() { return TransformManager.CONTENT_CLASS } @Override Set super QualifiedContent.Scope> getScopes() { return TransformManager.SCOPE_FULL_PROJECT } @Override boolean isIncremental() { return false } @Override void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { super.transform(transformInvocation) // OutputProvider管理輸出路徑,如果消費型輸入為空,你會發現OutputProvider == null TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); transformInvocation.inputs.each { TransformInput input -> input.jarInputs.each { JarInput jarInput -> // 處理Jar processJarInput(jarInput, outputProvider) } input.directoryInputs.each { DirectoryInput directoryInput -> // 處理源碼文件 processDirectoryInputs(directoryInput, outputProvider) } } } void processJarInput(JarInput jarInput, TransformOutputProvider outputProvider) { File dest = outputProvider.getContentLocation( jarInput.getFile().getAbsolutePath(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR) // to do some transform // 將修改過的字節碼copy到dest,就可以實現編譯期間干預字節碼的目的了 FileUtils.copyFiley(jarInput.getFile(), dest) } void processDirectoryInputs(DirectoryInput directoryInput, TransformOutputProvider outputProvider) { File dest = outputProvider.getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY) // 建立文件夾 FileUtils.forceMkdir(dest) // to do some transform // 將修改過的字節碼copy到dest,就可以實現編譯期間干預字節碼的目的了 FileUtils.copyDirectory(directoryInput.getFile(), dest) }}
2、有增量編譯
AspectJTransform.groovy代碼如下:
class AspectJTransform extends Transform { final String NAME = "JokerWanTransform" @Override String getName() { return NAME } @Override SetgetInputTypes() { return TransformManager.CONTENT_CLASS } @Override Set super QualifiedContent.Scope> getScopes() { return TransformManager.SCOPE_FULL_PROJECT } @Override boolean isIncremental() { return true } @Override void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { super.transform(transformInvocation) boolean isIncremental = transformInvocation.isIncremental() // OutputProvider管理輸出路徑,如果消費型輸入為空,你會發現OutputProvider == null TransformOutputProvider outputProvider = transformInvocation.getOutputProvider() if (!isIncremental) { // 不需要增量編譯,先清除全部 outputProvider.deleteAll() } transformInvocation.getInputs().each { TransformInput input -> input.jarInputs.each { JarInput jarInput -> // 處理Jar processJarInputWithIncremental(jarInput, outputProvider, isIncremental) } input.directoryInputs.each { DirectoryInput directoryInput -> // 處理文件 processDirectoryInputWithIncremental(directoryInput, outputProvider, isIncremental) } } } void processJarInputWithIncremental(JarInput jarInput, TransformOutputProvider outputProvider, boolean isIncremental) { File dest = outputProvider.getContentLocation( jarInput.getFile().getAbsolutePath(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR) if (isIncremental) { // 處理增量編譯 processJarInputWhenIncremental(jarInput, dest) } else { // 不處理增量編譯 processJarInput(jarInput, dest) } } void processJarInput(JarInput jarInput, File dest) { transformJarInput(jarInput, dest) } void processJarInputWhenIncremental(JarInput jarInput, File dest) { switch (jarInput.status) { case Status.NOTCHANGED: break case Status.ADDED: case Status.CHANGED: // 處理有變化的 transformJarInputWhenIncremental(jarInput.getFile(), dest, jarInput.status) break case Status.REMOVED: // 移除Removed if (dest.exists()) { FileUtils.forceDelete(dest) } break } } void transformJarInputWhenIncremental(JarInput jarInput, File dest, Status status) { if (status == Status.CHANGED) { // Changed的狀態需要先刪除之前的 if (dest.exists()) { FileUtils.forceDelete(dest) } } // 真正transform的地方 transformJarInput(jarInput, dest) } void transformJarInput(JarInput jarInput, File dest) { // to do some transform // 將修改過的字節碼copy到dest,就可以實現編譯期間干預字節碼的目的了 FileUtils.copyFile(jarInput.getFile(), dest) } void processDirectoryInputWithIncremental(DirectoryInput directoryInput, TransformOutputProvider outputProvider, boolean isIncremental) { File dest = outputProvider.getContentLocation( directoryInput.getFile().getAbsolutePath(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY) if (isIncremental) { // 處理增量編譯 processDirectoryInputWhenIncremental(directoryInput, dest) } else { processDirectoryInput(directoryInput, dest) } } void processDirectoryInputWhenIncremental(DirectoryInput directoryInput, File dest) { FileUtils.forceMkdir(dest) String srcDirPath = directoryInput.getFile().getAbsolutePath() String destDirPath = dest.getAbsolutePath() Map fileStatusMap = directoryInput.getChangedFiles() fileStatusMap.each { Map.Entry entry -> File inputFile = entry.getKey() Status status = entry.getValue() String destFilePath = inputFile.getAbsolutePath().replace(srcDirPath, destDirPath) File destFile = new File(destFilePath) switch (status) { case Status.NOTCHANGED: break case Status.REMOVED: if (destFile.exists()) { FileUtils.forceDelete(destFile) } break case Status.ADDED: case Status.CHANGED: FileUtils.touch(destFile) transformSingleFile(inputFile, destFile, srcDirPath) break } } } void processDirectoryInput(DirectoryInput directoryInput, File dest) { transformDirectoryInput(directoryInput, dest) } void transformDirectoryInput(DirectoryInput directoryInput, File dest) { // to do some transform // 將修改過的字節碼copy到dest,就可以實現編譯期間干預字節碼的目的了 FileUtils.copyDirectory(directoryInput.getFile(), dest) } void transformSingleFile(File inputFile, File destFile, String srcDirPath) { FileUtils.copyFile(inputFile, destFile) }}
延伸閱讀1:TransformInput
TransformInput是指輸入文件的一個抽象,包括:
DitectoryInput集合:是指以源碼的方式參與項目編譯的所有目錄結構及其目錄下的源碼文件JarInput集合:是指以jar包方式參與項目編譯的所有本地jar包和遠程jar包(此處的jar包包括aar)