Booster庫是什麼這裏就很少說了,本文嘗試經過源碼分析,表述對Booster工做流程及相關細節的理解。html
通過我的理解及梳理得出如下的說明圖,展現了Booster庫在Gradle的兩個重要生命週期中,發生的行爲:java
驟眼一看項目源碼會發現有不少模塊,感受不能一會兒掌控。不要緊,經過手敲模塊名稱,按名稱前綴規律、構建依賴共同點等特徵分類,我得出瞭如下的劃分:node
簡述android |
相關模塊api |
各模塊基礎依賴、kotlin的基礎擴展緩存 |
buildSrc、booster-kotlinx性能優化 |
對Project、AGP中的BaseVariant、VariantScope、TransformInvocation等類添加方法擴展,bash |
booster-gradle-api、booster-gradle-v3_0架構 booster-gradle-v3_二、booster-gradle-v3_3app |
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功能模塊 |
其餘模塊各自實現:性能檢測、性能優化、系統問題、應用瘦身等 |
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信息。
負責兩件事情:
BoosterAppTransform
和 BoosterLibTransform
,都是BoosterTransform
的子類。ServiceLoader
,運行時(Gradle自己是在JVM運行)動態查找VariantProcessor
的實現對象列表,遍歷並調用processor()
方法。對於抽象類com.android.build.api.transform.Transform,先來一段說明:
Content
爲後者的入參 Input
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去看。
代理默認的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後都發生了什麼?咱們接着往下看。
該模塊只有這些kt文件
AbstractInsnNode.kt
AsmTransformer
ClassNode.kt
ClassTransformer.kt
InsnList.kt
TypeInsnNode.kt複製代碼
除了AsmTransform
和ClassTransform
,其餘kt文件都是對ASM API的類進行方法擴展。
在這一層的職責,是把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相關的知識說明:
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
是找不到。
相比較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的依賴,保證自動執行具體的代碼,在下一篇文章中再說明。
在說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需用利用反射對某些對象進行代理置換,因此這個模塊負責提供工具方法。
主要對AGP API類進行方法擴展,以及定義一些transform中用到的類。
類型上是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
,並圍繞這個列表,提供最大名稱長度、最大構建文件長度等變量。
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的這套架構展現了上述知識點的」組合拳拳法「,特別是在架構設計上展現職責解耦、擴展靈活,值得模仿學習。
在下一篇文章中,我打算接着分析各個功能模塊的技術點和原理。