概述
Booster库是什么这里就不多说了,本文尝试通过源码分析,表述对Booster工作流程及相关细节的理解。
经过个人理解及梳理得出以下的说明图,展示了Booster库在Gradle的两个重要生命周期中,发生的行为:
骤眼一看项目源码会发现有很多模块,感觉不能一下子掌控。没关系,通过手敲模块名称,按名称前缀规律、构建依赖共同点等特征分类,我得出了以下的划分:
简述 | 相关模块 |
各模块基础依赖、kotlin的基础扩展 | buildSrc、booster-kotlinx |
对Project、AGP中的BaseVariant、VariantScope、TransformInvocation等类添加方法扩展, | booster-gradle-api、booster-gradle-v3_0 booster-gradle-v3_2、booster-gradle-v3_3 |
Booster的作用入口:自定义插件 + 注册自定义Transfrom。封装ASM层解析class文件成ClassNode并分发到下层具体功能实现。自定义ClassTransform、 VariantProcessor接口等提供扩展能力 | booster-gradle-plugin、booster-transform-asm booster-transform-javassist、booster-transform-spi booster-transform-util、booster-task-spi |
提供编译期对Android API插桩的能力 | booster-android-api、booster-android-instrument |
具体Booster功能模块 | 其他模块各自实现:性能检测、性能优化、系统问题、应用瘦身等 |
正文
1.buildSrc
apply from: ‘../gradle/booster.gradle'
booster-gradle-xxx、booster-transform-xxx等,非booster-android-instrument相关的模块都使用了该统一配置,应用kotlin的相关依赖配置。
并且引入定义在buildSrc目录的自定义Gradle插件’BuildPropsPlugin’。
'buildSrc'是Gradle约定的默认编译目录,效果等同于在settings.gradle中include,该目录是非必要的。
其作用是对项目中Gradle DSL无法简单实现的构建逻辑,进行补充使用。
【BuildPropsPlugin.kt】
让模块project的java、groovy、kotlin相关compile Task,依赖其自定义Task:generateBuildProps,在编译前创建一个Build接口类,包含当前module的GROUP、ARTIFACT、VERSION、REVISION等常量值,在运行过程中方便report信息。
2.booster-gradle-plugin
2.1 BoosterPlugin.kt
负责两件事情:
- 根据android app 和 library模块,分别注册
BoosterAppTransform
和BoosterLibTransform
,都是BoosterTransform
的子类。 - 利用Java的
ServiceLoader
,运行时(Gradle本身是在JVM运行)动态查找VariantProcessor
的实现对象列表,遍历并调用processor()
方法。
2.2 BoosterTransform.kt
对于抽象类com.android.build.api.transform.Transform,先来一段说明:
- Transform是在构建构件的过程中执行的过程
- 多个Transform会连接起来按顺序执行,前者产物
Content
为后者的入参Input
- 同时每个Transform可以设置要接收的content类型
ContentType
和范围Scope
,以及产出类型'ContentType',让整个链更高效 input
以TransformInput
类型集合接收内容,有两个属性jarInputs
和directoryInputs
,两者都有ContentType
和Scope
的信息- 利用
TransformOutputProvider
对象,可以创建本Transform独立的文件存储空间
BoosterTransform的执行过程是支持Gradle的增量编译,在transform()
方法的实现中,把回调的TransformInvocation
对象代理给BoosterTransformInvocation
。
流程逻辑:
增量:
-> onPreTransform()
-> doIncrementalTransform()
-> onPostTransform()
非增量:
-> 清理构建目录、输出目录
-> onPreTransfrom()
-> doFullTransform()
-> onPostTransform()
主要的处理差异在于doIncrementalTransform()
和 doFullTransform()
方法,细节实现都要往BoosterTransformInvocation.kt去看。
2.3 BoosterTransformInvocation.kt
代理默认的TransformInvocation
对象,并进行扩展,另外还实现了booster-transform-spi模块的相关接口:
TransformContext
: 封装了Booster在Transform
过程中的上下文数据:类路径、文件目录、应用信息、调试信息等TransformListener
: 定义transform的前后监听回调方法onPreTransform()
和onPostTransform()
ArtifactManager
: 对Artifact的管理,具体为构建过程的各种文件,get()
方法使用VariantScope
的各种方法一一对应实现。
关键成员属性:
// 使用SPI机制,读取Transformer接口的实现列表
private val transformers: List<Transformer!>
= ServiceLoader.load(Transformer::class.java,
javaClass.classLoader).toList()
对于关键doFullTransform()
和 doIncrementalTransform()
方法,主要了解两点:
- 如何实现增量构建的兼容
- 具体逻辑流程
构建过程中Transform
的回调,通过inputs:List<TransformInput>
属性获取资源。
public interface TransformInput {
/** Returns a collection of {@link JarInput}. */
@NonNull
Collection<JarInput> getJarInputs();
/** Returns a collection of {@link DirectoryInput}. */
@NonNull
Collection<DirectoryInput> getDirectoryInputs();
}
JarInput
和DirectoryInput
都是QualifiedContent
的子接口,提供方法获取资源的状态:
/**
* The file changed status for incremental execution.
*/
public enum Status {
/** 相对上次构建,没有变化.*/
NOTCHANGED,
/** 相对上次构建,属于新增文件.*/
ADDED,
/** 相对上次构建,有被修改. */
CHANGED,
/** 相对上次构建,被删除. */
REMOVED;
}
显而易见的「增量构建」是发生在两次构建之间的过程,这四种状态涵盖了两次构建中每个文件、文件夹的变化状况。
when (status) {
REMOVED -> 本次编译不需要该文件/文件夹,删除
ADDED, CHANGED -> 该文件/文件夹,需要被编译处理
NOT_CHANGED -> 不需要处理,因为已经有上次构建的.class文件
}
无论是JarInput
还是DirectoryInput
,都是这样兼容增量构建。
下面来看看input的内容都经历了什么。
File
、InputStream
和ByteArray
类,都利用Kotlin添加扩展方法transform
File.transform(output:File, transform:(ByteArray)->ByteArray)
fun File.transform(output: File, transformer: (ByteArray) -> ByteArray = { it -> it }) {
when {
isDirectory -> {
val base = this.toURI()
this.search().forEach {
it.transform(File(output, base.relativize(it.toURI()).path), transformer)
}
}
isFile -> {
when (output.extension.toLowerCase()) {
"jar" -> {
JarOutputStream(output.touch().outputStream()).use { dest ->
JarFile(this).use { jar ->
jar.entries().asSequence().forEach { entry ->
dest.putNextEntry(JarEntry(entry.name))
if (!entry.isDirectory) {
when (entry.name.substringAfterLast('.', "")) {
"class" -> jar.getInputStream(entry).use { src ->
logger.info("Transforming ${this.absolutePath}!/${entry.name}")
src.transform(transformer).redirect(dest)
}
else -> jar.getInputStream(entry).use { src ->
src.copyTo(dest)
}
}
}
}
}
}
}
"class" -> inputStream().use {
logger.info("Transforming ${this.absolutePath}")
it.transform(transformer).redirect(output)
}
else -> this.copyTo(output, true)
}
}
else -> TODO("Unexpected file: ${this.absolutePath}")
}
}
对于上述代码,我们可以进行以下的语义理解:
/* 语义理解:把当前File对象通过transform方法处理输出到output文件 */
Check Point 1 : (output, file) {
-> 检查file是文件目录Directory: 把子File一个个拿出排好队,
到「Check Point 2」 接受检查
-> 检查file是文件File: 到「Check Point 2」 接受检查
}
Check Point 2 : (output, file) {
-> 是'.jar'文件:装饰一下变成JarFile,把entry一个个掏出,
后缀是'class',拿去「Check Point 3」检查
-> 是'.class'文件:拿去「Check Point 3」检查
}
Check Point 3 : (output, classFile) {
-> classFile 转 InputStream,调用InputStream.transform()
}
InputStream.transform( transformer:(ByteArray)->ByteArray): ByteArray
// booster-transform-util模块的transform.kt中分别定义transform 和 readBytes方法扩展
fun InputStream.transform( transformer:(ByteArray)->ByteArray): ByteArray {
transformer(readBytes())
}
private fun InputStream.readBytes(estimatedSize: Int = DEFAULT_BUFFER_SIZE): ByteArray {
val buffer = ByteArrayOutputStream(Math.max(estimatedSize, this.available()))
copyTo(buffer)
return buffer.toByteArray()
}
ByteArray.transform(invocation: BoosterTransformInvocation): ByteArray
private fun ByteArray.transform(invocation: BoosterTransformInvocation): ByteArray {
return transformers.fold(this) { bytes, transformer ->
transformer.transform(invocation, bytes) // 成功接受transformer的洗礼
}
}
小结:让每个符合BoosterTransform
规定的ContentType
和Scope
的 .class
文件,都能接受ServiceLoader
加载到的Transform
的transform()
方法的“洗礼”。
问题来了:下层 Transformer
拿到ByteArray后都发生了什么?我们接着往下看。
3.booster-transform-asm
该模块只有这些kt文件
AbstractInsnNode.kt
AsmTransformer
ClassNode.kt
ClassTransformer.kt
InsnList.kt
TypeInsnNode.kt
除了AsmTransform
和ClassTransform
,其他kt文件都是对ASM API的类进行方法扩展。
3.1 AsmTransform.kt
在这一层的职责,是把ByteArray
-> ClassNode
,暴露给下一层进行方法修改操作,然后转换回ByteArray
返回上层。
使用auto-service库的@AutoService
注解,声明AsmTransform
按照SPI的规范生成相关的META-INF文件。
在build.gradle中声明依赖:
kapt
"com.google.auto.service:auto-service:1.0-rc4"
compile
'com.google.auto.service:auto-service:1.0-rc4'
保证了前面的BoosterTransformInvocation
能通过ServiceLoader
加载数据。
override fun transform(context: TransformContext, bytecode: ByteArray): ByteArray {
return ClassWriter(ClassWriter.COMPUTE_MAXS).also { writer ->
transformers.fold(ClassNode().also { klass ->
ClassReader(bytecode).accept(klass, 0)
}) { klass, transformer ->
transformer.transform(context, klass)
}.accept(writer)
}.toByteArray()
}
这段代码混合使用了kotlin的语法及高阶函数fold()
,让上层BoosterTransformInvocation
传入的bytecode:ByteArray
,经过ASM Core API的ClassWriter
、ClassReader
+ Tree API 的 ClassNode
,对ByteArray进行读取解析,然后通过累计遍历transformers,再次洗礼。
下面尝试换把AsmTransformer.transform()
方法的代码,换一个老套的写法,更容易理解:
val klass = ClassNode() // ClassNode是ASM tree API的类
// accept()方法,读取bytecode的相关.class信息赋值到klass对象的相应字段
ClassReader(bytecode).accept(klass, 0) // ClassReader是ASM core API的类
transformers.foreach { transformer ->
// 每次transform后,class的内容变化都缓存在klass中
// 注意此时的klass对象的内容就是bytecode的内容,只是格式不同而已
transformer.transform(context, klass)
}
val writer = ClassWriter(ClassWriter.COMPUTE_MAXS)// ClassWriter是ASM core API的类
klass.accept(writer) // 接收一个writer
// writer把被洗礼后的klass,即ClassNode对象,
// 写成ByteArray返回到上层BoosterTransformInvocation
return writer.toByteArray()
关于ASM相关的知识说明:
- ASM的模型基本理念、术语
- ASM的对class文件操作是基于JVM标准中,对class文件内容的规范来进行的,所以要理解class的相关规范,JVM执行模型及方法调用过程的工作原理、字节码指令的格式和运作、opcode格式等
- ASM Core API 和 Tree API的区别
- 了解API 对class和method的读写操作基本够用
3.2 ClassTransform.kt
ClassTransform是其他booster-transform-xxx模块,需要进行字节码插桩时候,需要具体实现的接口。
/**
* Represents class transformer
*
* @author johnsonlee
*/
interface ClassTransformer : TransformListener {
/**
* Transform the specified class node
*
* @param context The transform context
* @param klass The class node to be transformed
* @return The transformed class node
*/
fun transform(context: TransformContext, klass: ClassNode) = klass
}
实现者同时要实现TransformListener
,可以在transform执行前后,游刃有余地处理资源准备和释放。
当然啦,实现者也要使用@AutoService
注解,不然AsmTransform
是找不到。
4.booster-task-spi
相比较transform-spi,task-spi就显得简单一些了。
package com.didiglobal.booster.task.spi
import com.android.build.gradle.api.BaseVariant
interface VariantProcessor {
fun process(variant: BaseVariant)
}
其实现主要使用场景:
- 通过
BaseVariant
,添加自定义task,并作为编译task的依赖,保证自动执行 - 为project添加booster-android-instrument-xxx相关的模块依赖
具体的代码,在下一篇文章中再说明。
5.booster-android-instrument
在说instrument之前,先说booster-android-api模块。
看其源码你就会发现,它并没有提供什么实质的API。再看看:
噢,都是依赖类型compileOnly
,所以这个模块的作用,其实是为了booster-instrument-xxx模块中,编写HOOK代码的时候,能有一定的Android API代码编译环境,没什么特别。
回到booster-android-instrument,可以发现都是仅适用java,并没有使用kotlin。
CaughtCallback.java // 代理Handler, 捕获handleMessage()的RuntimeException
CaughtRunnable.java // 代理Runnable, 捕获run()的RuntimeException
Constants.java // android.util.Log的TAG,全局统一使用
Reflection.java // 抽象类,提供常用的反射静态方法
因为booster-android-instrument-xxx模块,是提供具体HOOK方法的实现,HOOK需用利用反射对某些对象进行代理置换,所以这个模块负责提供工具方法。
6.booster-android-gradle-api
主要对AGP API类进行方法扩展,以及定义一些transform中用到的类。
6.1 ResolvedArtifactResults.kt
类型上是ResolvedArtifactResult
的集合。
ResolvedArtifactResult
类图

ResolvedArtifactResults.kt中,关键属性的results初始化过程:
results =
listOf(AndroidArtifacts.ArtifactType.AAR, AndroidArtifacts.ArtifactType.JAR) //指定构件类型列表为AAR和JAR
.asSequence() // -> Sequence<AndroidArtifacts.ArtifactType>
.map { // map转换
variant.variantData.scope
.getArtifactCollection(
AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
AndroidArtifacts.ArtifactScope.ALL,
it)
} // -> Sequence<ArtifactCollection> 构件集合
.map {
it.artifacts // ArtifactCollection.getArtifacts()
} // -> Sequence<ResolvedArtifactResult>
.flatten() // 平铺
.filter { it.id.componentIdentifier !is ProjectComponentIdentifier } // 过滤ResolvedArtifactResult中,构件标识类型 != ProjectComponentIdentifier
.toList() // -> List<ResolvedArtifactResult>
.distinctBy { it.id.componentIdentifier.displayName } // 按构件名称去重
.sortedBy { it.id.componentIdentifier.displayName } // 按构件名称排序
.toList() // 得到结果
结论:ResolvedArtifactResults
解析、提取Android模块项目中,依赖构件文件类型为AAR和JAR,且构件标识为ProjectComponentIdentifier
类型的构件,并且按名称去重、排序,得到一个列表results
,并围绕这个列表,提供最大名称长度、最大构建文件长度等变量。
6.2 ComponentHandler.kt
class ComponentHandler : DefaultHandler() {
val applications = mutableSetOf<String>()
val activities = mutableSetOf<String>()
val services = mutableSetOf<String>()
val providers = mutableSetOf<String>()
val receivers = mutableSetOf<String>()
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
val name: String = attributes.getValue(ATTR_NAME) ?: return
when (qName) {
"application" -> applications.add(name)
"activity" -> activities.add(name)
"service" -> services.add(name)
"provider" -> providers.add(name)
"receiver" -> receivers.add(name)
}
}
}
专门解析AndroidManifest.xml,提取并缓存Application、及所有的四大组件信息列表。
总结
Gradle生命周期、ASM API、Java SPI机制、Kotlin语法、AGP,基本上涵盖了我在阅读源码时候需要运用的知识层面。
之于我个人,booster的这套架构展示了上述知识点的”组合拳拳法“,特别是在架构设计上展示职责解耦、扩展灵活,值得模仿学习。
在下一篇文章中,我打算接着分析各个功能模块的技术点和原理。