Transform API
存在于AGP
中,存在版本为4.2-7.0+
,后续在8.0时就会被移除。
AGP 1.5
引入的特性,主要用于在构建过程中,在Class->Dex
修改Class字节码,通过Transform可以获得Class文件。
再通过Javassist
或ASM
对字节码进行修改,插入自定义逻辑。
AGP插件版本说明
后面的分析基于以下版本:
AGP:7.2.1 本次采用依赖 “com.android.tools.build:gradle:7.2.1”来分析
Gradle:7.3.3
使用场景
埋点统计 :在页面展现和退出等生命周期中插入埋点统计代码,以统计页面展现数据
耗时监控 :在指定方法的前后插入耗时计算,以观察方法执行时间
方法替换 :将方法调用替换为调用另一个方法
信息读取:解析编译产生的.class文件,得到一些有用的数据,做其他操作
工作机制
使用Transform
无需关注相关task的生成与执行,主要在于处理输入的资源文件
工作时机 工作在Android构建过程中的.class Files -> Dex
节点
处理对象
javac 编译后的Class文件
resource资源
本地/远程依赖的jar/aar文件
每个Transform都对应一个Task,对应名称为 transform${inputTypes}With${TransformName}For${Variant}
,例如transformClassesWithMethodTraceForRelease
Transform 内的输入输出实际对应的就是Task 的输入输出。
最后Transform
输出的内容都会存储在${moduleName}/build/intermediates/transforms/${TransformName}/${Variant}
,例如app/build/intermediates/transforms/MethodTrace/release
TaskManager
将每个Transform Task
串连起来,前一个的输出会做为下一个的输入信息。
相关API 以下为Transform中的核心方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class TestTransform : Transform () { override fun getName () : String { TODO("Not yet implemented" ) } override fun getInputTypes () : MutableSet<QualifiedContent.ContentType> { TODO("Not yet implemented" ) } override fun getOutputTypes () : MutableSet<QualifiedContent.ContentType> { return super .getOutputTypes() } override fun getScopes () : MutableSet<in QualifiedContent.Scope> { TODO("Not yet implemented" ) } override fun isIncremental () : Boolean { TODO("Not yet implemented" ) } override fun transform (transformInvocation: TransformInvocation ?) { super .transform(transformInvocation) } }
getName
指定Transform
名称,后续也是对应的Task名称
Task命名规则为:transform${inputTypes}With${TransformName}For${Variant}
指定Transform
输入/输出类型,对象为ContentType
。
其中getOutputTypes
默认与getInputTypes
一致
ContentType——内容类型
输入或输出的内容类型。
* DefaultContentType(自定义时使用) 1 2 3 4 5 6 7 8 9 10 enum DefaultContentType implements ContentType { CLASSES(0x01 ), RESOURCES(0x02 ); }
CLASSES :Java字节码,包括Jar文件
RESOURCES :Java资源文件
ExtendedContentType(内部Transform使用) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public enum ExtendedContentType implements ContentType { DEX(0x1000 ), NATIVE_LIBS(0x2000 ), CLASSES_ENHANCED(0x4000 ), DATA_BINDING(0x10000 ), DEX_ARCHIVE(0x40000 ) }
通常使用TransformManager
设置类型
1 2 3 public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES);public static final Set<ContentType> CONTENT_JARS = ImmutableSet.of(CLASSES, RESOURCES);public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.of(RESOURCES);
示例代码 1 2 3 override fun getInputTypes () : MutableSet<QualifiedContent.ContentType> { return TransformManager.CONTENT_CLASS }
getScopes
指定Transform
处理哪些作用域的输入文件
Scope——处理范围 * Scope(自定义时使用) 1 2 3 4 5 6 7 8 9 10 11 12 enum Scope implements ScopeType { PROJECT(0x01 ), SUB_PROJECTS(0x04 ), EXTERNAL_LIBRARIES(0x10 ), TESTED_CODE(0x20 ), PROVIDED_ONLY(0x40 ) }
若为子Module注册Transform,则只能使用Scope.PROJECT
。
通常使用TransformManager
设置作用域
1 2 3 public static final Set<ScopeType> PROJECT_ONLY = ImmutableSet.of(Scope.PROJECT); public static final Set<ScopeType> SCOPE_FULL_PROJECT = ImmutableSet.of(Scope.PROJECT, Scope.SUB_PROJECTS, Scope.EXTERNAL_LIBRARIES);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public enum InternalScope implements QualifiedContent.ScopeType { MAIN_SPLIT(0x10000 ), LOCAL_DEPS(0x20000 ), FEATURES(0x40000 ), ; }
示例代码 1 2 3 4 5 6 7 8 override fun getScopes () : MutableSet<in QualifiedContent.Scope> { val set = mutableSetOf<QualifiedContent.Scope>() set .add(QualifiedContent.Scope.PROJECT) if (isApp) { set .add(QualifiedContent.Scope.EXTERNAL_LIBRARIES) } return set }
isIncremental
当前Transform
是否支持增量编译
不是每次的编译都是可以增量编译的,通过isIncremental
check当前是否为增量编译
false
:非增量编译 清空output目录,再对每个class/jar
进行处理
true
:增量编译,需要检查每个class/jar
的Status
,分别处理
存在两个标志位:
Transform
增量构建的开发
若为true
,对应的TransformTask
依然会执行,有可能触发增量构建
当前Transform
对应的Task
是否增量执行
若为true
,对应的TransformTask
表示增量模式。
在增量模式下,所有的Input
都是带Status
的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public enum Status { NOTCHANGED, ADDED, CHANGED, REMOVED; }
Status
分为以下四种:
NOTCHANGED:(文件无改变)无操作
ADDED:(文件新增) 按照正常流程处理 class/jar
CHANGED:(文件修改) 按照正常流程处理 class/jar
REMOVED:(文件删除) 清除outputProvider
对应路径文件
在这个方法中获取输入的Class文件,经过中间过程的修改,最后输出修改的Class文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public interface TransformInvocation { @NonNull Context getContext () ; @NonNull Collection<TransformInput> getInputs () ; @NonNull Collection<TransformInput> getReferencedInputs () ; @NonNull Collection<SecondaryInput> getSecondaryInputs () ; @Nullable TransformOutputProvider getOutputProvider () ; boolean isIncremental () ; }
主要包括以下方法:
getContext :返回Transform
运行相关信息
getInputs :返回TransformInput
对象,主要是输入内容信息
getOutputProvider :返回TransformOutputProvider
对象,主要是返回输出文件
isIncremental :当前Transform
对应Task是否增量构建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface TransformInput { @NonNull Collection<JarInput> getJarInputs () ; @NonNull Collection<DirectoryInput> getDirectoryInputs () ; }
主要包括以下两个方法:
getJarInputs:Collection:以jar和aar的依赖方式参与构建的输入文件,包含本地依赖和远程依赖
getDirectoryInputs:Collection:以源码方式参与项目编译的所有目录结构及其中的源码文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public interface TransformOutputProvider { void deleteAll () throws IOException ; @NonNull File getContentLocation ( @NonNull String name, @NonNull Set<QualifiedContent.ContentType> types, @NonNull Set<? super QualifiedContent.Scope> scopes, @NonNull Format format) ; }
主要包括以下两个方法:
执行流程 获取输入文件 主要对象为TransformInput
,对应的文件分为两种:
源码文件 / DirectoryInput
依赖的Jar/aar文件 / JarInput
获取输出路径 主要对象为TransformOutputProvider
,需要采用指定方法获取,输出路径主要是两种:
源码文件输出路径
1 2 3 4 5 6 val dest = outputProvider.getContentLocation( dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY )
依赖的Jar/aar文件输出路径
1 2 3 4 5 6 val dest = outputProvider.getContentLocation( jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR )
主要区别在设置的Format
上。
处理输入文件 处理上按照输入文件类型分开处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 override fun transform (transformInvocation: TransformInvocation ) { val context = transformInvocation.context val inputs = transformInvocation.inputs val outputProvider = transformInvocation.outputProvider val isIncremental = transformInvocation.isIncremental if (!isIncremental) { outputProvider.deleteAll() } inputs.forEach { input -> input.directoryInputs.forEach { directoryInput -> foreachDirectory(context, outputProvider, directoryInput, isIncremental) } input.jarInputs.forEach { jarInput -> foreachJar(context, outputProvider, jarInput, isIncremental) } } }
源码文件(src下编译生成的class文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 private fun foreachDirectory (context: Context , outputProvider: TransformOutputProvider , input: DirectoryInput , isIncremental: Boolean ) { val dir = input.file val dest = outputProvider.getContentLocation( input.name, input.contentTypes, input.scopes, Format.DIRECTORY ) FileUtils.forceMkdir(dest) val srcDicPath = dir.absolutePath val destDicPath = dest.absolutePath if (isIncremental) { val fileStatus = input.changedFiles fileStatus.forEach { file, status -> val destFilePath = file.absolutePath.replace(srcDicPath, destDicPath) val destFile = File(destFilePath) when (status) { Status.ADDED, Status.CHANGED -> { println("File is Updated name: ${file.name} and path ${file.absolutePath} " ) transformDir(context,dir,inputFile,destFilePath) } Status.REMOVED -> { println("File is Removed name: ${file.name} " ) if (destFile.exists()) { destFile.delete() } } else -> { } } }else { FileUtils.copyDirectory(dir, dest) dir.walk().asSequence().filter { it.isFile && checkDicClassFile(it.name) }.forEach { file -> transformDir(context, dir, file, file.absolutePath.replace(srcDirPath, destDirPath)) } } } private fun transformDir (context: Context , dir: File , inputFile: File , destFilePath: String ) { val destFile = File(destFilePath) if (destFile.exists()) { destFile.delete() } val modifiedFile = modifyClassFile(dir,inputFile,context.temporaryDir) if (modifiedFile != null ) { FileUtils.copyFile(modifiedFile, destFile) modifiedFile.delete() } else { FileUtils.copyFile(inputFile, destFile) } }
注意⚠️:就算不想修改class文件,也需要原样拷贝过去 。否则该文件就会丢失!
Jar/aar文件
相比于class文件多了解压缩过程,解压后得到的class文件处理方式与上面一致,最后需要将处理后的class文件重新压缩即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 private fun foreachJar (context: Context , outputProvider: TransformOutputProvider , input: JarInput , isIncremental: Boolean ) { if (input.file.absolutePath.endsWith("jar" )) { var jarName = input.file.name val md5Name = DigestUtils.md5Hex(input.file.absolutePath) if (jarName.endsWith(".jar" )) { jarName = jarName.substring(0 , jarName.length - 4 ) } val dest = outputProvider.getContentLocation( "${jarName} _${md5Name} " , input.contentTypes, input.scopes, Format.JAR ) if (isIncremental) { when (input.status) { Status.ADDED, Status.CHANGED -> { println("Jar is Updated name: ${input.file.name} " ) transformJar(context, dest, input) } Status.REMOVED -> { println("Jar is Removed and ${input.file.name} " ) if (dest.exists()) { FileUtils.forceDelete(dest) } } else -> { } } } else { transformJar(context, dest, input) } } } private fun transformJar (context: Context , dest: File , input: JarInput ) { var modifyJar: File? = null modifyJar = modifyJarFile(input.file, context.temporaryDir) if (modifyJar == null ) { modifyJar = input.file } FileUtils.copyFile(modifyJar, dest) }
flowchart TB
id1((开始))
id2(transform)
id1 --> id2
id3{isIncremental}
id2--> id3
id4(outputProvider.deleteAll)
id3--非增量模式/false-->id4
id5(遍历transformInvocation.inputs)
id4-->id5
id3--增量模式/true-->id5
id7(遍历\ndirectoryInputs)
id6(遍历\njarInputs)
id5-->id6
id5-->id7
id71(遍历\nsrc下class文件)
id72(执行hook)
id73(拷贝修改后class文件到\noutputProvider.getContentLocation位置)
id7-->id71-->id72-->id73
id61(遍历jarinput下jar/aar文件)
id62(解压jar/aar文件)
id63(遍历内部class文件)
id64(执行hook)
id65(修改完的class文件打包成jar文件)
id66(拷贝修改后jar文件到\noutputProvider.getContentLocation位置)
id6-->id61-->id62-->id63-->id64-->id65-->id66
id8((transform\n结束))
id66-->id8
id73-->id8
实现一个Transform
后,需要注册才可以功能生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class CustomPlugin : Plugin <Project > { override fun apply (project: Project ) { ... val appExtension = project.extensions.findByType(AppExtension::class .java ) if (appExtension != null ) { appExtension.registerTransform(MethodTraceTransform(project, true )) } else { val libExtension = project.extensions.findByType(LibraryExtension::class .java ) libExtension?.registerTransform(MethodTraceTransform(project, false )) } } }
工作原理
主要是将Transform
注册在BaseExtension
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 abstract class BaseExtension protected constructor ( ... private val _transforms: MutableList<Transform> = mutableListOf() private val _transformDependencies: MutableList<List<Any>> = mutableListOf() ... fun registerTransform (transform: Transform , vararg dependencies: Any ) { dslServices.deprecationReporter.reportDeprecatedApi( newApiElement = null , oldApiElement = "android.registerTransform" , url = "https://developer.android.com/studio/releases/gradle-plugin-api-updates#transform-api" , deprecationTarget = DeprecationReporter.DeprecationTarget.TRANSFORM_API ) _transforms.add(transform) _transformDependencies.add(listOf(dependencies)) } override val transforms: List<Transform> get () = ImmutableList.copyOf(_transforms) override val transformsDependencies: List<List<Any>> get () = ImmutableList.copyOf(_transformDependencies) }
内部的transforms
由外部调用,GlobalTaskCreationConfigImpl
使用到了transforms
1 2 override val transforms: List<Transform> get () = oldExtension.transforms
GlobalTaskCreationConfigImpl
为GlobalTaskCreationConfig
实现类,所以GlobalTaskCreationConfig
对应的transforms
即为BaseExtension
注册进来的Transform
。
由Gradle学习笔记-AGP原理 可知Task的构建流程
都是由BasePlugin.createAndroidTasks
开始的,其他细节在Gradle学习笔记-AGP原理 有详细介绍
BasePlugin.createAndroidTasks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 final void createAndroidTasks () { GlobalTaskCreationConfig globalConfig = variantManager.getGlobalTaskCreationConfig(); ... TaskManager<VariantBuilderT, VariantT> taskManager = createTaskManager( project, variants, variantManager.getTestComponents(), variantManager.getTestFixturesComponents(), globalConfig, taskManagerConfig, extension); taskManager.createTasks(variantFactory.getVariantType(), createVariantModel(globalConfig)); }
TaskManager.createTasks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fun createTasks ( variantType: VariantType , variantModel: VariantModel ) { ... for (variant in variants) { createTasksForVariant(variant) } }private fun createTasksForVariant ( variant: ComponentInfo <VariantBuilderT , VariantT>, ) { ... doCreateTasksForVariant(variant) }protected abstract fun doCreateTasksForVariant ( variantInfo: ComponentInfo <VariantBuilderT , VariantT>)
TaskManager.doCreateTasksForVariant
TaskManager
是抽象类,主要实现类为
ApplicationTaskManager
:对应 app module
LibraryTaskManager
:对应 library module
以LibraryTaskManager
为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Override protected void doCreateTasksForVariant ( @NotNull ComponentInfo<LibraryVariantBuilderImpl, LibraryVariantImpl> variantInfo) { ... TransformManager transformManager = libraryVariant.getTransformManager(); List<Transform> customTransforms = globalConfig.getTransforms(); List<List<Object>> customTransformsDependencies = globalConfig.getTransformsDependencies(); for (int i = 0 , count = customTransforms.size(); i < count; i++) { Transform transform = customTransforms.get(i); Sets.SetView<? super Scope> difference = Sets.difference(transform.getScopes(), TransformManager.PROJECT_ONLY); if (!difference.isEmpty()) { String scopes = difference.toString(); issueReporter.reportError( Type.GENERIC, String.format( "Transforms with scopes '%s' cannot be applied to library projects." , scopes)); } transformManager.addTransform( taskFactory, libraryVariant, transform, null , task -> { if (!deps.isEmpty()) { task.dependsOn(deps); } }, taskProvider -> { if (transform.getScopes().isEmpty()) { TaskFactoryUtils.dependsOn( libraryVariant.getTaskContainer().getAssembleTask(), taskProvider); } }); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @NonNull public <T extends Transform> Optional<TaskProvider<TransformTask>> addTransform( @NonNull TaskFactory taskFactory, @NonNull VariantCreationConfig creationConfig, @NonNull T transform, @Nullable PreConfigAction preConfigAction, @Nullable TaskConfigAction<TransformTask> configAction, @Nullable TaskProviderCallback<TransformTask> providerCallback) { String taskName = creationConfig.computeTaskName(getTaskNamePrefix(transform), "" ); return Optional.of( taskFactory.register( new TransformTask.CreationAction<>( creationConfig.getName(), taskName, transform, inputStreams, referencedStreams, outputStream), preConfigAction, wrappedConfigAction, providerCallback)); } static String getTaskNamePrefix (@NonNull Transform transform) { StringBuilder sb = new StringBuilder(100 ); sb.append("transform" ); sb.append( transform .getInputTypes() .stream() .map( inputType -> CaseFormat.UPPER_UNDERSCORE.to( CaseFormat.UPPER_CAMEL, inputType.name())) .sorted() .collect(Collectors.joining("And" ))); sb.append("With" ); StringHelper.appendCapitalized(sb, transform.getName()); sb.append("For" ); return sb.toString(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @CacheableTask public abstract class TransformTask extends StreamBasedTask { @Input @NonNull public Set<? super QualifiedContent.Scope> getScopes() { return transform.getScopes(); } @Input @NonNull public Set<QualifiedContent.ContentType> getInputTypes() { return transform.getInputTypes(); } @OutputDirectory @Optional @NonNull public abstract DirectoryProperty getOutputDirectory () ; @OutputFile @Optional @NonNull public abstract RegularFileProperty getOutputFile () ; ... public static class CreationAction <T extends Transform > extends TaskCreationAction <TransformTask > { @NonNull private final String variantName; @NonNull private final String taskName; @NonNull private final T transform; ... @Override public void configure (@NonNull TransformTask task) { task.transform = transform; transform.setOutputDirectory(task.getOutputDirectory()); transform.setOutputFile(task.getOutputFile()); ... } } }
最后创建一个名为transform[getInputTypes]With[getName]For[creationConfig.name]
的Task,并注册到当前module中。
执行Task
实际执行的是自定义Task
内部实现了@TaskAction
的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @TaskAction void transform (final IncrementalTaskInputs incrementalTaskInputs) throws IOException, TransformException, InterruptedException { ... transform.transform( new TransformInvocationBuilder(context) .addInputs(consumedInputs.getValue()) .addReferencedInputs(referencedInputs.getValue()) .addSecondaryInputs(changedSecondaryInputs.getValue()) .addOutputProvider( outputStream != null ? outputStream.asOutput() : null ) .setIncrementalMode(isIncremental.getValue()) .build()); }
最终执行到了自定义Transform
的transform()
。
//todo 流程
增量模式实现
若isIncremental
为true,则返回的文件会携带状态,根据不同的状态执行不同的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private static void gatherChangedFiles ( @NonNull Logger logger, @NonNull IncrementalTaskInputs incrementalTaskInputs, @NonNull final Map<File, Status> changedFileMap, @NonNull final Set<File> removedFiles) { logger.info("Transform inputs calculations based on following changes" ); incrementalTaskInputs.outOfDate(inputFileDetails -> { logger.info(inputFileDetails.getFile().getAbsolutePath() + ":" + IntermediateFolderUtils.inputFileDetailsToStatus(inputFileDetails)); if (inputFileDetails.isAdded()) { changedFileMap.put(inputFileDetails.getFile(), Status.ADDED); } else if (inputFileDetails.isModified()) { changedFileMap.put(inputFileDetails.getFile(), Status.CHANGED); } }); incrementalTaskInputs.removed( inputFileDetails -> { logger.info(inputFileDetails.getFile().getAbsolutePath() + ":REMOVED" ); removedFiles.add(inputFileDetails.getFile()); }); }
由Gradle
中的TaskExecution.execute()
处理对应的文件。
会在{%post_link Gradle学习笔记-构建流程%}详细分析。
flowchart LR
id(compileReleaseJavaWithJavac\nTask产物)
id--->id1
id--->id2
id--->id3
subgraph JarInput
id2(jar/aar)
end
subgraph DirectoryInput
id1(class)
id3(resource)
end
id4(自定义Transform)
id1-.->id4
id2-.->id4
id3-.->id4
id4--->id5
id5(自定义Transform)
id5-.->id6
id6(系统Transform)
基于上面分析,每个Transform
都对应一个TransformTask
,Android编译器中的TaskManager
将每个Transform
串连起来。
第一个自定义Transform
接受来自compileJavaWithJavac(对应源码 JavaCompileCreationAction)
Task的中间产物
javac编译得到的class文件
远端/本地的第三方依赖
resource资源
这些产物在Transform链
上传递,处理完成后再向下一个进行传递。
大部分功能代码是一致的,主要差异在于字节码的处理上,由此可以抽象出一套模板写法,实现方只要处理字节码部分即可。
Transform
API将在AGP 8.0
中移除,主要为了提高构建性能,Transform API很难与其他Gradle特性结合使用。
基于以上原因,Transform
后续可以使用AsmClassVisitorFactory
或TransformAction
进行替代。
AsmClassVisitorFactory
在AGP 4.2
后提供AsmClassVisitorFactory
,主要用于处理class
文件,与Transform
主要功能大致相同,可以看做同等替换。
根据官方的说法,AsmClassVisitoFactory
会带来约18%的性能提升,同时可以减少约5倍代码
AsmClassVisitorFactory 文档
示例代码 新建ClassVisitorFactory对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 abstract class ExampleClassVisitorFactory : AsmClassVisitorFactory <ExampleClassVisitorFactory.ExampleParams > { interface ExampleParams : InstrumentationParameters { @get:Input val writeToStdout: Property<Boolean > } override fun createClassVisitor (classContext: ClassContext , nextClassVisitor: ClassVisitor ) : ClassVisitor { val className = classContext.currentClassData.className.substringAfterLast("." ) return ExampleClassNode(className, nextClassVisitor, PrintWriter(System.out )) } override fun isInstrumentable (classData: ClassData ) : Boolean { return !classData.className.contains(".R$" ) && !classData.className.endsWith(".R" ) && !classData.className.endsWith(".BuildConfig" ) && classData.className.startsWith("com.example" ) } }class ExampleClassNode (private val className: String, private val nextVisitor: ClassVisitor, private val printWriter: PrintWriter) : ClassVisitor(Opcodes.ASM7, nextVisitor) { override fun visitMethod ( access: Int , name: String ?, descriptor: String ?, signature: String ?, exceptions: Array <out String >? ) : MethodVisitor { val methodVisitor = super .visitMethod(access, name, descriptor, signature, exceptions) return PrintLogInterceptor(className, methodVisitor, access, name, descriptor) } inner class PrintLogInterceptor ( private val className: String?, private val methodVisitor: MethodVisitor, access: Int , name: String?, descriptor: String? ) : AdviceAdapter(ASM7, methodVisitor, access, name, descriptor) { override fun onMethodEnter () { super .onMethodEnter() methodVisitor.visitLdcInsn(className) methodVisitor.visitLdcInsn(name) methodVisitor.visitMethodInsn(INVOKESTATIC, "android/util/Log" , "d" , "(Ljava/lang/String;Ljava/lang/String;)I" , false ) } } }
注册ClassVisitorFactory对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class CustomPlugin : Plugin <Project > { override fun apply (project: Project ) { val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class .java ) //获取androidComponent if (androidComponents!=null ){ androidComponents.onVariants {variant -> variant.instrumentation.transformClassesWith( ExampleClassVisitorFactory::class .java , InstrumentationScope.ALL //扫描范围 ){ it.writeToStdout.set (true ) } variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES) } } } }
可使用 Android Studio Plugin——ASM ByteCode Viewer
,提前把要插入的代码进行解析获取ASM代码。
相关API AsmClassVisitorFactory 1 2 3 4 5 6 7 8 9 10 11 interface AsmClassVisitorFactory <ParametersT : InstrumentationParameters > : Serializable { @get:Nested val parameters: Property<ParametersT> fun createClassVisitor ( classContext: ClassContext , nextClassVisitor: ClassVisitor ) : ClassVisitor fun isInstrumentable (classData: ClassData ) : Boolean }
外部可配置ClassVisitorFactory
的属性
设置哪些类需要被扫描,可以提升执行效率
对应类的部分数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 interface ClassData { val className: String val classAnnotations: List<String> val interfaces: List<String> val superClasses: List<String> }
创建ClassVisitor
对象
AndroidComponentsExtension transformClassesWith
注册自定义ClassVisitorFactory
工作原理 与Transform
类似,实际AsmClassVisitorFactory
最终也是通过Task执行,使用的就是TransformClassesWithAsm
Task。
初始也是从BasePlugin.createAndroidTasks
开始,再执行到TaskManager.doCreateTasksForVariant
。
TaskManager.doCreateTasksForVariant TaskManager
是抽象类,主要实现类为
ApplicationTaskManager
:对应 app module
LibraryTaskManager
:对应 library module
以ApplicationTaskManager
为例
ApplicationTaskManager.doCreateTasksForVariant 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 override fun doCreateTasksForVariant ( variantInfo: ComponentInfo <ApplicationVariantBuilderImpl , ApplicationVariantImpl> ) { createCommonTasks(variantInfo) ... }protected void createCommonTasks(@NonNull ComponentInfo<VariantBuilderT, VariantT> variant) { ... createCompileTask(appVariantProperties); } private void createCompileTask(@NonNull VariantImpl variant) { ApkCreationConfig apkCreationConfig = (ApkCreationConfig) variant; TaskProvider<? extends JavaCompile> javacTask = createJavacTask(variant); addJavacClassesStream(variant); setJavaCompilerTask(javacTask, variant); createPostCompilationTasks(apkCreationConfig); } fun createPostCompilationTasks (creationConfig: ApkCreationConfig ) { ... maybeCreateTransformClassesWithAsmTask( creationConfig as ComponentImpl, isTestCoverageEnabled ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 protected fun maybeCreateTransformClassesWithAsmTask ( creationConfig: ComponentImpl , isTestCoverageEnabled: Boolean ) { ... creationConfig .transformManager .consumeStreams( mutableSetOf(com.android.build.api.transform.QualifiedContent.Scope.PROJECT), setOf(com.android.build.api.transform.QualifiedContent.DefaultContentType.CLASSES)) taskFactory.register( TransformClassesWithAsmTask.CreationAction( creationConfig, isTestCoverageEnabled ) ) }
在此处注册TransformClassesWithAsmTask
实际执行 字节码处理的 任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 abstract class TransformClassesWithAsmTask : NewIncrementalTask () { ... @get:Input abstract val excludes: SetProperty<String> @get:Nested abstract val visitorsList: ListProperty<AsmClassVisitorFactory<*>> @get:Incremental @get:Classpath abstract val inputClassesDir: ConfigurableFileCollection @get:Incremental @get:Classpath @get:Optional abstract val inputJarsDir: DirectoryProperty @get:OutputDirectory abstract val classesOutputDir: DirectoryProperty @get:OutputDirectory abstract val jarsOutputDir: DirectoryProperty ... override fun doTaskAction (inputChanges: InputChanges ) { if (inputChanges.isIncremental) { doIncrementalTaskAction(inputChanges) } else { doFullTaskAction(inputChanges) } } }
doFullTaskAction/doIncrementalTaskAction
全量执行和增量执行任务,根据isIncremental
判断是否增量编译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private fun doFullTaskAction (inputChanges: InputChanges ) { incrementalFolder.mkdirs() FileUtils.deleteDirectoryContents(classesOutputDir.get ().asFile) FileUtils.deleteDirectoryContents(incrementalFolder) workerExecutor.noIsolation().submit(TransformClassesFullAction::class .java ) { configureParams(it, inputChanges) it.inputClassesDir.from(inputClassesDir) it.inputJarsDir.set (inputJarsDir) } }private fun doIncrementalTaskAction (inputChanges: InputChanges ) { ... workerExecutor.noIsolation().submit(TransformClassesIncrementalAction::class .java ) { configureParams(it, inputChanges) it.inputClassesDirChanges.set ( inputChanges.getFileChanges(inputClassesDir).toSerializable() ) if (inputJarsDir.isPresent) { it.inputJarsChanges.set (inputChanges.getFileChanges(inputJarsDir).toSerializable()) } } }
最终执行对应的Action
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 abstract class TransformClassesFullAction : TransformClassesWorkerAction <FullActionWorkerParams > () { override fun run () { val classesHierarchyResolver = getClassesHierarchyResolver() getInstrumentationManager(classesHierarchyResolver).use { instrumentationManager -> parameters.inputClassesDir.files.filter(File::exists).forEach { instrumentationManager.instrumentClassesFromDirectoryToDirectory( it, parameters.classesOutputDir.get ().asFile ) } processJars(instrumentationManager) } updateIncrementalState(emptySet(), classesHierarchyResolver) } ... } abstract class TransformClassesIncrementalAction : TransformClassesWorkerAction <IncrementalWorkerParams > () { override fun run () { classesChanges.addedFiles.plus(classesChanges.modifiedFiles).forEach { val outputFile = parameters.classesOutputDir.get ().asFile.resolve(it.normalizedPath) instrumentationManager.instrumentModifiedFile( inputFile = it.file, outputFile = outputFile, relativePath = it.normalizedPath ) } processJars(instrumentationManager) } } fun processJars (instrumentationManager: AsmInstrumentationManager ) { ... mappingState.jarsInfo.forEach { (file, info) -> if (info.hasChanged) { val instrumentedJar = File(parameters.jarsOutputDir.get ().asFile, info.identity + DOT_JAR) FileUtils.deleteIfExists(instrumentedJar) instrumentationManager.instrumentClassesFromJarToJar(file, instrumentedJar) } } }
以上instrumentXX()
最终都指向doInstrumentClass()
AsmInstrumentationManager.doInstrumentClass 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private fun doInstrumentClass ( packageName: String , className: String , classInputStream: () -> InputStream ) : ByteArray? { ... val classData = ClassDataLazyImpl( classFullName, { classesHierarchyResolver.getAnnotations(classInternalName) }, { classesHierarchyResolver.getAllInterfaces(classInternalName) }, { classesHierarchyResolver.getAllSuperClasses(classInternalName) } ) val filteredVisitors = visitors.filter { entry -> entry.isInstrumentable(classData) }.reversed() ... return @use doInstrumentByteCode( classContext, byteCode, filteredVisitors, containsJsrOrRetInstruction = true ) }
接下来执行自定义的 字节码处理逻辑
AsmInstrumentationManager.doInstrumentByteCode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private fun doInstrumentByteCode ( classContext: ClassContext , byteCode: ByteArray , visitors: List <AsmClassVisitorFactory <*>>, containsJsrOrRetInstruction: Boolean ) : ByteArray { val classReader = ClassReader(byteCode) val classWriter = FixFramesClassWriter( classReader, getClassWriterFlags(containsJsrOrRetInstruction), classesHierarchyResolver ) var nextVisitor: ClassVisitor = classWriter if (framesComputationMode == FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_CLASSES) { nextVisitor = MaxsInvalidatingClassVisitor(apiVersion, classWriter) } val originalVisitor = nextVisitor visitors.forEach { entry -> nextVisitor = entry.createClassVisitor(classContext, nextVisitor) } if (nextVisitor == originalVisitor) { return byteCode } classReader.accept(nextVisitor, getClassReaderFlags(containsJsrOrRetInstruction)) return classWriter.toByteArray() }
在doInstrumentByteCode
执行自定义处理逻辑
flowchart TD
TransformClassesWithAsmTask执行流程
a((开始))
b(doTaskAction)
a-->b
c{isIncremental\n是否增量编译?}
b-->c
d(doFullTaskAction)
c--全量编译-->d
d1(执行\nTransformClassesFullAction)
d2(处理class/jar文件)
d-->d1-->d2
e(doIncrementalTaskAction)
c--增量编译-->e
e1(TransformClassesIncrementalAction)
e2(处理增量class/jar文件\nFileStatus.NEW或FileStatus.CHANGED)
e-->e1-->e2
f(AsmInstrumentationManager\n.doInstrumentClass)
d2-->f
e2-->f
g(筛选isInstrumentable=true\n的AsmClassVisitorFactory)
h(AsmInstrumentationManager\n.doInstrumentByteCode)
f-->g
g-->h
i(执行自定义 字节码处理 逻辑)
h-->i
j((结束))
i-->j
通过AndroidComponentsExtension
注册自定义的AsmClassVisitorFactory
对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class .java ) if (androidComponents!=null ){ androidComponents.onVariants {variant -> println("variant.name == ${variant.name} " ) variant.instrumentation.transformClassesWith( ExampleClassVisitorFactory::class .java , InstrumentationScope.ALL ){ it.writeToStdout.set (true ) } variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES) } }
AndroidComponentsExtension结构如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 interface AndroidComponentsExtension < DslExtensionT: CommonExtension<*, *, *, * >, VariantBuilderT: VariantBuilder , VariantT: Variant> : DslLifecycle<DslExtensionT>{...}interface Variant : Component , HasAndroidResources {...} interface Component : ComponentIdentity { ... val instrumentation: Instrumentation }interface Instrumentation { ... fun <ParamT : InstrumentationParameters> transformClassesWith ( classVisitorFactoryImplClass: Class <out AsmClassVisitorFactory <ParamT >>, scope: InstrumentationScope , instrumentationParamsConfig: (ParamT ) -> Unit ) }class InstrumentationImpl ( services: TaskCreationServices, variantPropertiesApiServices: VariantPropertiesApiServices, private val isLibraryVariant: Boolean ) : Instrumentation { private val asmClassVisitorsRegistry = AsmClassVisitorsFactoryRegistry(services.issueReporter) override fun <ParamT : InstrumentationParameters> transformClassesWith ( classVisitorFactoryImplClass: Class <out AsmClassVisitorFactory <ParamT >>, scope: InstrumentationScope , instrumentationParamsConfig: (ParamT ) -> Unit ) { if (isLibraryVariant && scope == InstrumentationScope.ALL) { throw RuntimeException( "Can't register ${classVisitorFactoryImplClass.name} to " + "instrument library dependencies.\n" + "Instrumenting library dependencies will have no effect on library " + "consumers, move the dependencies instrumentation to be done in the " + "consuming app or test component." ) } asmClassVisitorsRegistry.register( classVisitorFactoryImplClass, scope, instrumentationParamsConfig ) } }
AsmClassVisitorsFactoryRegistry.register 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class AsmClassVisitorsFactoryRegistry (private val issueReporter: IssueReporter) { val projectClassesVisitors = ArrayList<AsmClassVisitorFactoryEntry<out InstrumentationParameters>>() val dependenciesClassesVisitors = ArrayList<AsmClassVisitorFactoryEntry<out InstrumentationParameters>>() fun <ParamT : InstrumentationParameters> register ( classVisitorFactoryImplClass: Class <out AsmClassVisitorFactory <ParamT >>, scope: InstrumentationScope , instrumentationParamsConfig: (ParamT ) -> Unit ) { val visitorEntry = AsmClassVisitorFactoryEntry( classVisitorFactoryImplClass, instrumentationParamsConfig ) if (scope == InstrumentationScope.ALL) { dependenciesClassesVisitors.add(visitorEntry) } projectClassesVisitors.add(visitorEntry) } }
通过transformClassesWith
注册的ClassVisitorFactory
最终记录在projectClassesVisitors
中。
AsmClassVisitorsFactoryRegistry.projectClassesVisitors
–>InstrumentationImpl.registeredProjectClassesVisitors
–>ComponentImpl.registeredProjectClassesVisitors
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected fun getInstrumentationManager ( classesHierarchyResolver: ClassesHierarchyResolver ) : AsmInstrumentationManager { return AsmInstrumentationManager( visitors = parameters.visitorsList.get (), apiVersion = parameters.asmApiVersion.get (), classesHierarchyResolver = classesHierarchyResolver, framesComputationMode = parameters.framesComputationMode.get (), excludes = parameters.excludes.get (), profilingTransforms = parameters.profilingTransforms.getOrElse(emptyList()) ) }class CreationAction ( component: ComponentImpl, val isTestCoverageEnabled: Boolean ) : VariantTaskCreationAction<TransformClassesWithAsmTask, ComponentImpl>( component ) { ... override fun configure (task: TransformClassesWithAsmTask ) { super .configure(task) task.incrementalFolder = creationConfig.paths.getIncrementalDir(task.name) task.visitorsList.setDisallowChanges(creationConfig.registeredProjectClassesVisitors) ... } }
优点&缺点 优点
不需要像Transform
写一堆模版代码,包括全量/增量文件处理,jar包处理,TransformClassesWithAsmTask
内部已实现相关逻辑
可注册多个AsmClassVisitorFactory
,在一次IO过程中,进行多次处理,提升构建性能。
缺点
内部实现与ASM
绑定,需要有一定的认知了解
不像Transform
那样灵活,可以进行除字节码处理以外的操作(基本也够用)
参考链接 Transform-API
Gradle-Transform 分析
TransformAction介绍
TransformAction-Gradle文档