Gradle 相關(guān)總結(jié)
APT 和 AGPTransform 區(qū)別
Gradle+Transform+Asm自動(dòng)化注入代碼
Android 360加固+Walle多渠道自動(dòng)化打包上傳蒲公英
最近將公司的項(xiàng)目進(jìn)行重構(gòu),將原本的模塊化進(jìn)行了組件化,在這個(gè)過(guò)程中遇到了很多,最典型的就是如何去初始化其他組件,比如:消息組件,而組件化最主要的是其他組件能夠單獨(dú)運(yùn)行和集成到殼工程,也就是說(shuō)業(yè)務(wù)組件又多種形態(tài),那么有個(gè)問(wèn)題就是怎么去初始化業(yè)務(wù)組件,業(yè)務(wù)組件在單獨(dú)運(yùn)行時(shí)能將初始化可以自己的在Application中去做初始化,而當(dāng)成是Library時(shí)就需要將宿主工程的Application下發(fā)給組件,怎么下發(fā)宿主工程的上下文到組件中?下面有幾種:
- 在BaseApp中直接初始化,但是這樣耦合度就非常高了,已經(jīng)背離了組件化的初衷;
- 在公共組件中定義IComponent接口,并通過(guò)配置(注解\文件\SPI)實(shí)現(xiàn)類(lèi)的全類(lèi)名,然后通過(guò)反射實(shí)現(xiàn)業(yè)務(wù)組件的初始化,實(shí)際上SPI底層是通過(guò)通過(guò)解析文件得到類(lèi)名通過(guò)反射實(shí)現(xiàn)的;
- 在Manifest中配置,然后解析Manifest文件,讀取到類(lèi)的全路徑,然后通過(guò)反射實(shí)現(xiàn)組件初始化;
上面是我目前所知道組件初始化的方式雖然能夠解決初始化問(wèn)題,但是都存在缺點(diǎn),那么除了上面所說(shuō)的方式,我們是否還有更好的方式去做到解耦合并且不會(huì)造成性能損耗呢?
在編譯時(shí),掃描即將打包到apk中的所有類(lèi)的字節(jié)碼,將所有組件類(lèi)收集起來(lái),通過(guò)修改字節(jié)碼的方式生成注冊(cè)代碼到組件管理類(lèi)中,從而實(shí)現(xiàn)編譯時(shí)自動(dòng)注冊(cè)的功能,不用再關(guān)心項(xiàng)目中有哪些組件類(lèi)了。
特點(diǎn):不需要注解,不會(huì)增加新的類(lèi);性能高,不需要反射,運(yùn)行時(shí)直接調(diào)用組件的構(gòu)造方法;能掃描到所有類(lèi),不會(huì)出現(xiàn)遺漏,而且還可以添加組件優(yōu)先排序。怎么實(shí)現(xiàn)呢?
AGP Transform
如果大家了解過(guò)apk打包的過(guò)程那么一定會(huì)知道Android 提供的Transform API,在Android apk打包過(guò)程中會(huì)利用 Transform 去完成每一部分的操作,并且會(huì)有輸入和輸出,比如DexTransform,是將class字節(jié)碼轉(zhuǎn)換成dex文件,那么輸入就是class字節(jié)碼,而輸出就是.dex文件,ProguardTransform則是完成混淆的,實(shí)際上Transform 是Android 提供的一種特殊Task,Task也是有輸入和輸出。
AGP Transform API是從Gradle 1.5.0版本之后提供的,它允許第三方在打包Dex文件之前的編譯過(guò)程中修改字節(jié)碼,在自定義插件中注冊(cè)的Transform會(huì)在ProguardTransform和DexTransform之前執(zhí)行,實(shí)際上Transform是Android一種特殊的Task,自定義的Transform是會(huì)在自帶的Transform之前執(zhí)行,所以自動(dòng)注冊(cè)的Transform不需要考慮混淆的情況。
APK 打包流程
我們平時(shí)在開(kāi)發(fā)的過(guò)程中,每天在Android Studio Run項(xiàng)目,Android Studio就會(huì)將apk自動(dòng)安裝到手機(jī)上了,那么這中間都經(jīng)歷過(guò)哪些流程呢,來(lái)看看官方的項(xiàng)目構(gòu)建流程圖

如圖所示,典型 Android 應(yīng)用模塊的構(gòu)建流程通常按照以下步驟執(zhí)行:
*1、 編譯器將源代碼轉(zhuǎn)換成 DEX 文件(Dalvik 可執(zhí)行文件),并將其他所有內(nèi)容轉(zhuǎn)換成編譯后的資源。
2、 APK 打包器將 DEX 文件和編譯后的資源合并到一個(gè) APK 中。不過(guò),在將應(yīng)用安裝并部署到 Android 設(shè)備之前,必須先為 APK 簽名。
3、APK 打包器使用調(diào)試或發(fā)布密鑰庫(kù)為 APK 簽名:
- 如果您構(gòu)建的是調(diào)試版應(yīng)用(即專(zhuān)用于測(cè)試和分析的應(yīng)用),則打包器會(huì)使用調(diào)試密鑰庫(kù)為應(yīng)用簽名。Android Studio 會(huì)自動(dòng)使用調(diào)試密鑰庫(kù)配置新項(xiàng)目。
- 如果您構(gòu)建的是打算對(duì)外發(fā)布的發(fā)布版應(yīng)用,則打包器會(huì)使用發(fā)布密鑰庫(kù)為應(yīng)用簽名。要?jiǎng)?chuàng)建發(fā)布密鑰庫(kù),請(qǐng)參閱在 Android Studio 中為應(yīng)用簽名。
4、 在生成最終 APK 之前,打包器會(huì)使用 zipalign 工具對(duì)應(yīng)用進(jìn)行優(yōu)化,以減少其在設(shè)備上運(yùn)行時(shí)所占用的內(nèi)存。
可能從圖中并不會(huì)看出什么來(lái),實(shí)際上對(duì)于Java編程語(yǔ)言來(lái)說(shuō),這個(gè)過(guò)程要從Java源代碼到apk,那么我們來(lái)看一張圖:

這張圖就非常的清晰了,gradle打包過(guò)程中基本上是通過(guò)官方提供的Transform完成的,文章開(kāi)始我就說(shuō)了自動(dòng)注入就是通過(guò)自定義Transform并將自定義Transform注冊(cè)到自定義gradle插件中,而卻我們自定義的Transform是優(yōu)先于ProguardTransform執(zhí)行的,所以不會(huì)造成因?yàn)榛煜鵁o(wú)法掃描到類(lèi)信息。
自定義Gradle 插件
Gradle官方文檔目前定義插件只有3中方式:
Build script,即:腳本插件,直接在構(gòu)建腳本(build.gradle)中直接寫(xiě)插件的代碼,編譯器會(huì)自動(dòng)將插件編譯并添加到構(gòu)建腳本的classpath中。但是該插件在構(gòu)建腳本之外是不可見(jiàn)的,所以不能在定義它的構(gòu)建腳本之外重用該插件。buildSrc project,執(zhí)行Gradle時(shí)會(huì)將根目錄下的buildSrc目錄作為插件源碼目錄進(jìn)行編譯,并將編譯結(jié)果加入到構(gòu)建腳本的classpath中,所以對(duì)整個(gè)項(xiàng)目是可用的,方便調(diào)試插件。Standalone project,在獨(dú)立項(xiàng)目中開(kāi)發(fā)插件,然后將項(xiàng)目打成jar包,發(fā)布到本地或者maven服務(wù)器上,可在多個(gè)項(xiàng)目間復(fù)用,不好調(diào)試插件。最后,除了第一種方式,后面兩種方式都是可以發(fā)布到本地或者maven服務(wù)器上,提供給其他項(xiàng)目使用的。所以我推薦
buildSrc project這種方式開(kāi)發(fā)插件。
我最后選擇的是 buildSrc project 開(kāi)發(fā)插件。
配置自定義gradle 插件的環(huán)境
1、首先在工程下新建一個(gè)java Libray項(xiàng)目,把其他無(wú)用的資源文件和目錄刪掉就保留src目錄和build.gradle文件;
2、在main目錄下新建resources/MATE-INF/gradle-plugins目錄,如:

并在gradle-plugins目下新建xxx.properties文件,而xxx是可以隨意定義名稱(chēng),而這個(gè)名稱(chēng)(xxx)就是你的插件的名稱(chēng),以后要引用該插件你可以通過(guò) apply plugin: 'xxx' 方式引用插件。
當(dāng)然除了這種配置還有另一中比較簡(jiǎn)單的配置方式:
apply plugin: 'java-gradle-plugin'
gradlePlugin {
plugins {
create('compoentPlugin') {
id = 'xxx'
implementationClass = 'com.github.plugin.ModuleComponentPluginKt'
}
}
}
gradle 中可以這樣定義Plugin ,并不一定在resources/META-INF/gradle-plugins/xxx.properties中定義插件。
3、xxx.properties文件中的內(nèi)容就是
implementation-class=com.github.plugin.ModuleComponentPluginKt
implementation-class是固定寫(xiě)法,而com.github.plugin.ModuleComponentPluginKt就是你的自定義插件類(lèi)的全類(lèi)名,同一個(gè)項(xiàng)目中還可以定義多個(gè)插件,你可以按照功能分插件引入項(xiàng)目。
4、build.gradle配置
apply plugin: 'groovy'
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'kotlin-android-extensions'
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
mavenCentral()
jcenter()
google()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
sourceSets {
main {
groovy {
srcDir '../buildSrc/src/main/groovy'
}
java {
srcDir "../buildSrc/src/main/java"
}
kotlin {
srcDir "../buildSrc/src/main/kotlin"
}
resources {
srcDir '../buildSrc/src/main/resources'
}
}
}
dependencies {
repositories {
mavenCentral()
jcenter()
google()
}
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation gradleApi()
implementation localGroovy()
implementation group: 'org.ow2.asm', name: 'asm', version: '7.1'
implementation group: 'org.ow2.asm', name: 'asm-commons', version: '7.1'
implementation 'com.android.tools.build:gradle:3.4.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
sourceCompatibility = "8"
targetCompatibility = "8"
因?yàn)槲业牟寮鞘褂玫腒otlin編寫(xiě),所以這build.gradle的配置會(huì)有kotlin的配置,以及asm的依賴(lài)等等相關(guān)。
Transform Api Android 提供的,所以你必須引入這個(gè)依賴(lài):implementation 'com.android.tools.build:gradle:3.4.2'
開(kāi)發(fā)gradle 插件
正如xxx.properties文件中定義的全類(lèi)名,所以在com.github.plugin包下
ModuleComponentPluginKt類(lèi),讓ModuleComponentPluginKt實(shí)現(xiàn)至org.gradle.api.Plugin接口,代碼如下:
class ModuleComponentPluginKt : Plugin<Project> {
private lateinit var mProject: Project
override fun apply(project: Project) {
this.mProject = project
KLogger.inject(project.logger)
KLogger.e("自定義插件ModuleComponentPluginKt")
PluginInitializer.initial(project)
if (project.plugins.hasPlugin(AppPlugin::class.java)) {
// 監(jiān)聽(tīng)每個(gè)任務(wù)的執(zhí)行時(shí)間
project.gradle.addListener(BuildTimeListener())
val android = project.extensions.getByType(AppExtension::class.java)
//主要操作就是收集滿(mǎn)足條件的類(lèi)
android.registerTransform(ScannerComponentTransformKt())
//收集完畢,在這里完成代碼的織入
android.registerTransform(ScannerAfterTransformKt())
}
}
}
在ModuleComponentPluginKt 類(lèi)中,通過(guò)project獲取到AppExtension并調(diào)用registerTransform方法將我們自定義的Transform注冊(cè)到AppExtension,而AppExtension就是application plugins。也就是App module的apply plugin: 'com.android.application'插件為com.android.application。
在ModuleComponentPluginKt 中還有 PluginInitializer.initial(project)是什么意思呢?這個(gè)也比較重要,代碼如下:
object PluginInitializer {
fun initial(project: Project) {
val hasAppPlugin = project.plugins.hasPlugin(AppPlugin::class.java)
val hasLibPlugin = project.plugins.hasPlugin(LibraryPlugin::class.java)
if (!hasAppPlugin && !hasLibPlugin) {
throw GradleException("Component: The 'com.android.application' or 'com.android.library' plugin is required.")
}
this.project = project
// 創(chuàng)建extensions ,可以通過(guò)extensions.getByType拿到這個(gè)拓展對(duì)象
project.extensions.create(COMPONENT_CONFIG_NAME, ComponentExtension::class.java)
}
lateinit var project: Project
}
在 PluginInitializer 類(lèi)中比較重要的這行代碼
project.extensions.create("componentExt", ComponentExtension::class.java)
拓展類(lèi):
open class ComponentExtension {
var matcherInterfaceType: String = "" //組件實(shí)現(xiàn)接口 如:com/github/plugin/common/IComponent
var matcherManagerTypeMethod: String = "" //管理類(lèi)初始化方法 如: initComponent
var matcherManagerType: String = "" //管理類(lèi)的全類(lèi)名 如:com/github/plugin/common/InjectManager
}
這行代碼就是給插件創(chuàng)建拓展(extensions)名字是componentExt,為什么會(huì)創(chuàng)建extensions,先看看使用就明白:
componentExt {
matcherInterfaceType "com.github.plugin.common.IComponent"
matcherManagerType "com.github.plugin.common.InjectManager"
matcherManagerTypeMethod "initComponent"
}
是不是明白了extensions的作用了,其實(shí)就是我們需要提供開(kāi)發(fā)者動(dòng)態(tài)的配置一些信息,這樣會(huì)更靈活。
Android Transform 結(jié)合Asm字節(jié)碼插樁完成代碼自動(dòng)注入(重點(diǎn))
自定義插件的代碼比較簡(jiǎn)單,基本上都是套路,通過(guò)拿到AppExtension并將我們自定義的Transform注冊(cè)進(jìn)去,看看那Transform的代碼,感興趣可以去Transform API
class ScannerComponentTransformKt : Transform() {
override fun getName(): String {
return "scanner_component_result"
}
override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
return TransformManager.CONTENT_CLASS
}
override fun isIncremental(): Boolean {
return false
}
override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
return TransformManager.SCOPE_FULL_PROJECT
}
override fun transform(transformInvocation: TransformInvocation) {
if (!transformInvocation.isIncremental) {
transformInvocation.outputProvider.deleteAll()
}
transformInvocation.inputs.forEach { input ->
input.directoryInputs.forEach { dirInput ->
//處理完輸入文件之后,要把輸出給下一個(gè)任務(wù),就是在:transforms\ScannerComponentTransformKt\debug\0目錄中
// name就是會(huì)在__content__.json文件中的name,唯一的,隨便取,但是一定要保證唯一
val dest = transformInvocation.outputProvider.getContentLocation(DigestUtils.md5Hex(dirInput.name),
dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY).also(FileUtils::forceMkdir)
//1、遍歷目錄中的文件;
//2、修改這些文件;
//3、然后將這些修改過(guò)的文件,復(fù)制到transforms的輸出目錄,那么為什么將這些修改過(guò)的文件放到transforms,
// 就會(huì)被打包到apk中呢?因?yàn)槲覀冏远x的transforms會(huì)優(yōu)先于其他transform執(zhí)行并且是優(yōu)先于其他的執(zhí)行,詳細(xì)的
//可以去看看BaseExtension的構(gòu)造方法
dirInput.file.eachFileRecurse { file ->
// dest===> transforms\ScannerComponentTransformKt\debug\0 D8編譯成dex文件
// file===> build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\com\github\plugin\examlple\MainActivity.class javac 編譯生成的字節(jié)碼
//現(xiàn)在來(lái)認(rèn)證一下,通過(guò)asm修改的字節(jié)碼,是否在javac 或 transforms中?
//確實(shí)會(huì)存在于transforms目錄中,但是javac中不存在
if (TypeUtil.isMatchCondition(file.name)) {
val outputFile = File(file.absolutePath.replace(dirInput.file.absolutePath, dest.absolutePath))
FileUtils.touch(outputFile)
//Dest目錄: build\intermediates\transforms\ScannerComponentTransformKt\debug\0
//輸入文件: build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\com\github\plugin\examlple\Inject.class
//輸出文件: build\intermediates\transforms\ScannerComponentTransformKt\debug\0\com\github\plugin\exalple\Inject.class
KLogger.e("inputFile: ${file.absolutePath} outputFile: ${outputFile.absolutePath} destFile: ${dest.absolutePath}")
val inputStream = FileInputStream(file)
// 開(kāi)始織入代碼,修改這些文件,即:對(duì)輸入的文件進(jìn)行修改
val bytes = WeaveSingleClass.weaveSingleClassToByteArray(inputStream)//需要織入代碼
//修改輸入文件完畢復(fù)制輸出文件中
val fos = FileOutputStream(outputFile)
fos.write(bytes)
fos.close()
inputStream.close()
}
}
//這里和上面的處理是一樣的,將目錄中的文件復(fù)制到dest目錄中
// FileUtils.copyDirectory(dirInput.file, dest)
}
//首先jar是需要解壓因?yàn)閖ar是通過(guò)zip進(jìn)行壓縮的
// TODO 多模塊需要處理Jar,因?yàn)閘ib最后打包是已jar形式引入
//common\build\intermediates\runtime_library_classes\debug\classes.jar
//usercenter\build\intermediates\runtime_library_classes\debug\classes.jar
input.jarInputs.forEach { jarInput ->
if (jarInput.file.absolutePath.endsWith(".jar")) {
//用于存放臨時(shí)操作的class文件,當(dāng)操作完畢,便將臨時(shí)文件拷貝到dest文件即可
val tmpFile = File(jarInput.file.parent + File.separator + "classes_temp.jar")
if (tmpFile.exists()) tmpFile.delete() //避免上次的緩存被重復(fù)插入
val tmpJarOutputStream = JarOutputStream(FileOutputStream(tmpFile))
//jar文件
val jarFile = JarFile(jarInput.file)
//拿到所有的jar中的文件
val enumeration = jarFile.entries()
//用于保存JAR文件,修改JAR中的class
while (enumeration.hasMoreElements()) {
val jarEntry = enumeration.nextElement()
val entryName = jarEntry.name
val zipEntry = ZipEntry(entryName)
if (zipEntry.isDirectory) continue
//讀取jar中的文件輸入流
val inputStream = jarFile.getInputStream(jarEntry)
//插樁class
if (TypeUtil.isMatchCondition(entryName)) {
KLogger.e("ASM 開(kāi)始處理Jar文件中${entryName}文件")
tmpJarOutputStream.putNextEntry(zipEntry)
val updateCodeBytes = WeaveSingleClass.weaveSingleClassToByteArray(inputStream)
tmpJarOutputStream.write(updateCodeBytes)
KLogger.e("ASM 結(jié)束處理Jar文件中${entryName}文件")
} else {
KLogger.e("不滿(mǎn)足條件Jar文件中${entryName}文件")
tmpJarOutputStream.putNextEntry(zipEntry)
tmpJarOutputStream.write(IOUtils.toByteArray(inputStream))
}
tmpJarOutputStream.closeEntry()
}
//結(jié)束
tmpJarOutputStream.close()
jarFile.close()
// 將臨時(shí)class文件拷貝到目標(biāo)dest文件
var jarName = jarInput.name//重名名輸出文件,因?yàn)榭赡芡?會(huì)覆蓋
val md5Name = DigestUtils.md5Hex(jarInput.file.absolutePath)
//截取.jar,即 去掉.jar name就是會(huì)在__content__.json文件中的name,唯一的
// name就是會(huì)在__content__.json文件中的name,唯一的,隨便取,但是一定要保證唯一
if (jarName.endsWith(".jar")) jarName = jarName.substring(0, jarName.length - 4)
val dest = transformInvocation.outputProvider.getContentLocation(jarName + md5Name,
jarInput.contentTypes, jarInput.scopes, Format.JAR)
//input: build\intermediates\runtime_library_classes\debug\classes.jar
// //output: build\intermediates\transforms\ScannerComponentTransformKt\debug\0.jar
// //KLogger.e("input: ${jarInput.file.absolutePath} output: ${dest.absolutePath}")
// //KLogger.e("${jarInput.name} $jarName ${jarName + md5Name}")
FileUtils.copyFile(tmpFile, dest)
tmpFile.delete()
}
}
}
KLogger.e("transform..................end")
}
}
可以看到在Transform的transform方法中通過(guò)directoryInputs和jarInputs就可以拿到目錄下的.class文件和Jar中的.class文件,也叫輸入數(shù)據(jù),這里我叫上游,而TransformOutputProvider的getContentLocation方法就是輸出,也叫下游。
注意:在Transform中,無(wú)論是否更新或修改某個(gè)輸入文件,你都必須將這些輸入文件復(fù)制到指定Transform的目錄中,不然打包的APK是找不到類(lèi)的。即: ATransform(上游) 的輸出則作為 BTransform(下游)的輸入,而這個(gè)過(guò)程就是中最后的產(chǎn)物就是APK。
其實(shí)整個(gè)構(gòu)建流程可以比作是以工廠流水線,而Transform則是流水線上專(zhuān)門(mén)負(fù)責(zé)特定某個(gè)任務(wù)節(jié)點(diǎn)。即:上一個(gè)節(jié)點(diǎn)的輸出則作為下一個(gè)節(jié)點(diǎn)的輸入,所以字節(jié)碼插裝就是得益于Android 給我們提供的這個(gè)機(jī)制。
asm操作的是class字節(jié)碼,在整個(gè)構(gòu)建過(guò)程中所有的字節(jié)碼都是在Transform中作為輸入,所以我們只需要遍歷Transform的輸入數(shù)據(jù),對(duì)于我們的Transform而言就是收集滿(mǎn)足條件的字節(jié)碼文件,然后通過(guò)asm織入一個(gè)我們的指定管理類(lèi)即可,對(duì)于Transform Api我不過(guò)多的介紹,網(wǎng)上很多博客寫(xiě)得非常好,大家可以去看看。
還是那句話Transform 不管你是否修改class或不修改class這個(gè)class輸入文件,都必須復(fù)制到指定transform的目錄中,不然打包的apk是找不到類(lèi)的,比如:你的MainActivity 繼承Androidx 的AppCompatActivity,那么如果你不處理AppCompatActivity的Jar,就會(huì)奔潰拋出ClassFileNotFoundException異常。但是你將MainActivity 的父類(lèi)繼承為Activity,那么就不會(huì)奔潰,因?yàn)锳ctivity屬于 Android.jar,而 Android.jar 則屬于系統(tǒng)類(lèi),Transform不會(huì)對(duì)android.jar中class做任何搜集和處理,即Transform你必須處理文件并將其寫(xiě)入輸出文件夾。即使不處理類(lèi)文件,你仍然必須將它們復(fù)制到輸出文件夾。如果你不這樣做,所有的類(lèi)文件都會(huì)被刪除。
asm 代碼如下:
object WeaveSingleClass {
fun weaveSingleClassToByteArray(inputStream: InputStream): ByteArray {
//1、解析字節(jié)碼
val classReader = ClassReader(inputStream)
//2、修改字節(jié)碼
val classWriter = ExtendClassWriter(ClassWriter.COMPUTE_MAXS)
val customClassVisitor = CustomInjectClassVisitor(classWriter)
//3、開(kāi)始解析字節(jié)碼
classReader.accept(customClassVisitor, ClassReader.EXPAND_FRAMES)
return classWriter.toByteArray()
}
fun weaveSingleClassToByteArrayAutoInject(inputStream: InputStream): ByteArray {
//1、解析字節(jié)碼
val classReader = ClassReader(inputStream)
//2、修改字節(jié)碼
val classWriter = ExtendClassWriter(ClassWriter.COMPUTE_MAXS)
val customClassVisitor = AutoInjectComponentClassVisitor(classWriter)
//3、開(kāi)始解析字節(jié)碼
classReader.accept(customClassVisitor, ClassReader.EXPAND_FRAMES)
return classWriter.toByteArray()
}
}
// 訪問(wèn)class信息
class AutoInjectComponentClassVisitor(classVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM7, classVisitor) {
//如果是實(shí)現(xiàn)了IComponent接口的話,將所有組件類(lèi)收集起來(lái),通過(guò)修改字節(jié)碼的方式生成注冊(cè)代碼到組件管理類(lèi)中
override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<out String>?) {
KLogger.e("${interfaces?.joinToString { it }}")
KLogger.e("name>>>----$name")
if (interfaces?.contains(PluginInitializer.getComponentInterfaceName()) == true && name != "") {
ComponentNameCollection.add("$name")
}
super.visit(version, access, name, signature, superName, interfaces)
}
override fun visitMethod(access: Int, name: String, descriptor: String, signature: String?, exceptions: Array<out String>?): MethodVisitor {
KLogger.e("name:$name descriptor:$descriptor")
val visitMethod = super.visitMethod(access, name, descriptor, signature, exceptions)
if (PluginInitializer.getComponentManagerTypeInitMethodName() != name) {
return visitMethod
}
return AutoInjectComponentMethodVisitor(visitMethod, access, name, descriptor)
}
}
// 訪問(wèn)method信息
class AutoInjectComponentMethodVisitor(methodVisitor: MethodVisitor?, access: Int, name: String?, descriptor: String?)
: AdviceAdapter(Opcodes.ASM7, methodVisitor, access, name, descriptor) {
override fun onMethodExit(opcode: Int) {
KLogger.e("${ComponentNameCollection.size} $opcode")
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(GETFIELD, PluginInitializer.getComponentManagerTypeName(), "components", "Ljava/util/List;")
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "clear", "()V", true)
ComponentNameCollection.forEach { name ->
KLogger.e(">><<<>>>>>>${name}")
// 加載this
mv.visitVarInsn(ALOAD, 0)
//拿到類(lèi)的成員變量 坑,你需要注意的類(lèi)名不要寫(xiě)錯(cuò)了
mv.visitFieldInsn(GETFIELD, PluginInitializer.getComponentManagerTypeName().replace(".", "/"), "components", "Ljava/util/List;")
//用無(wú)參構(gòu)造方法創(chuàng)建一個(gè)組件實(shí)例
mv.visitTypeInsn(Opcodes.NEW, name)
mv.visitInsn(Opcodes.DUP)
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, name, "<init>", "()V", false)
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true)
mv.visitInsn(POP)
}
}
}
最后產(chǎn)生的字節(jié)碼之前和之后對(duì)比如下:
public class InjectManager {
public synchronized void initComponent() { }
}
..........之后..........
public class InjectManager {
private List<IComponent> components = new ArrayList();
public synchronized void initComponent() {
this.components.clear();
this.components.add(new MainComponent());
this.components.add(new UserComponent());
this.components.add(new OrderComponent());
}
}
這樣就完成了組件化在編譯期自動(dòng)注入其他組件初始化,當(dāng)你要使用的就直接調(diào)用InjectManager .initComponent()就可以了。其實(shí)還有更好的方式就是像 android hilt 那樣通過(guò)修改類(lèi)的繼承方式,把所有的邏輯放在了父類(lèi)中,讓我們Application 去繼承該Application即可。
為什么我會(huì)定義IComponent接口并讓所有初始化組件實(shí)現(xiàn),這是因?yàn)楹笃诳赡茉黾右恍┢渌δ芑虿僮?,比如:增加組件初始化的優(yōu)先級(jí),那么 IComponent接口 直接增加一個(gè)優(yōu)先級(jí)方法即可完成,而不需要在去修改我們織入字節(jié)碼的操作,那太復(fù)雜了容易出錯(cuò)。
參考借鑒文章:
我看很多人留言說(shuō)需要代碼,我最近給大家寫(xiě)了個(gè)模板,希望對(duì)大家有用:
abstract class IncrementalTransform extends Transform {
// 共享線程池
//protected final WaitableExecutor globalSharedThreadPool = WaitableExecutor.useGlobalSharedThreadPool()
protected final ThreadPool threadPool = new ThreadPool()
private Project project
IncrementalTransform(Project project) {
this.project = project
}
@Override
void transform(TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException {
doTransform(transformInvocation)
}
private void doTransform(TransformInvocation invocation) {
TransformOutputProvider outputProvider = invocation.outputProvider
if (!invocation.isIncremental()) {
outputProvider.deleteAll()
}
invocation.inputs.each { TransformInput transformInput ->
// JAR
transformInput.jarInputs.each { JarInput jarInput ->
threadPool.addTask(new ITask() {
@Override
Void call() throws Exception {
return handleJar(jarInput, outputProvider, invocation)
}
})
}
// DIR
transformInput.directoryInputs.each { DirectoryInput directoryInput ->
threadPool.addTask(new ITask() {
@Override
Void call() throws Exception {
return handleDirectory(directoryInput, outputProvider, invocation)
}
})
}
}
//等待所有任務(wù)結(jié)束
//globalSharedThreadPool.waitForTasksWithQuickFail(true)
threadPool.startWork()
}
private void handleJar(
JarInput jarInput,
TransformOutputProvider outputProvider,
TransformInvocation invocation) {
//得到上一個(gè)Transform輸入文件
File inputJar = jarInput.file
// 得到當(dāng)前Transform輸出Jar文件
File outputJar =
outputProvider.getContentLocation(
jarInput.name, jarInput.contentTypes,
jarInput.scopes, Format.JAR)
if (invocation.isIncremental()) {// 增量處理
if (jarInput.status == Status.NOTCHANGED) {//文件沒(méi)有改變
println("IncrementalTransform >>> File NOTCHANGED")
} else if (jarInput.status == Status.ADDED) {//有新增文件
dispatchAction(inputJar, outputJar, true)
} else if (jarInput.status == Status.CHANGED) {//有修改文件
FileUtils.deleteIfExists(outputJar)// 先把上次生成的文件刪除
dispatchAction(inputJar, outputJar, true)
} else if (jarInput.status == Status.REMOVED) {//文件被移除
//把上次當(dāng)前Transform輸出文件刪除
FileUtils.delete(outputJar)
}
} else {// 全量處理
dispatchAction(inputJar, outputJar, true)
}
}
private void handleDirectory(
DirectoryInput directoryInput, TransformOutputProvider outputProvider,
TransformInvocation invocation) {
//得到上一個(gè)Transform輸入文件目錄
File inputDir = directoryInput.file
// 得到當(dāng)前Transform輸出文件目錄
File outputDir =
outputProvider.getContentLocation(
directoryInput.name, directoryInput.contentTypes,
directoryInput.scopes, Format.DIRECTORY)
if (invocation.isIncremental()) {
directoryInput.changedFiles.entrySet().each { Map.Entry<File, Status> entry ->
File inputFile = entry.key
if (entry.value == Status.NOTCHANGED) {//文件沒(méi)有改變
println("IncrementalTransform >>> File NOTCHANGED")
} else if (entry.value == Status.ADDED) {//有增加文件
File outputFile = FileUtil.toOutputFile(outputDir, inputDir, inputFile)
dispatchAction(inputFile, outputFile, false)
} else if (entry.value == Status.CHANGED) {//文件有修改
File outputFile = FileUtil.toOutputFile(outputDir, inputDir, inputFile)
FileUtils.deleteIfExists(outputFile)// 先把上次生成的文件刪除
dispatchAction(inputFile, outputFile, false)
} else if (entry.value == Status.REMOVED) {//文件被移除
//把上次輸出的目錄刪除
File outputFile = FileUtil.toOutputFile(outputDir, inputDir, inputFile)
FileUtils.deleteIfExists(outputFile)
}
}
} else {
// 上一個(gè)Transform的輸出目錄下的所有文件
FluentIterable<File> dirChildFiles = FileUtils.getAllFiles(inputDir)
dirChildFiles.each { File inputFile ->
// 當(dāng)前Transform輸出文件
File outputFile = FileUtil.toOutputFile(outputDir, inputDir, inputFile)
dispatchAction(inputFile, outputFile, false)
}
}
}
protected void dispatchAction(
File inputFile, File outputFile, boolean handleJar) {
if (handleJar) {//JAR
// 輸出目標(biāo)jar文件
FileOutputStream fos = new FileOutputStream(outputFile)
JarOutputStream outputJarOs = new JarOutputStream(fos)
//處理輸入Jar文件
JarFile inputJarFile = new JarFile(inputFile)
Enumeration<JarEntry> entries = inputJarFile.entries()
while (entries.hasMoreElements()) {
JarEntry inputJarEntry = entries.nextElement()
String inputJarEntryName = inputJarEntry.getName()
// 拿到j(luò)ar包里面的輸入流
InputStream inputJarEntryInputStream =
inputJarFile.getInputStream(inputJarEntry)
// 構(gòu)造目標(biāo)jar文件里的文件實(shí)體 即保持和上一個(gè)JarEntry名稱(chēng)一致
outputJarOs.putNextEntry(new ZipEntry(inputJarEntryName))
//如果不做是否處理,那么僅僅只是將該文件復(fù)制到當(dāng)前Transform的目標(biāo)輸出文件即可
boolean isHandle =
doJarAction(inputJarEntryInputStream, outputJarOs)
if (!isHandle) {
//將修改過(guò)的字節(jié)碼copy到dest
outputJarOs.write(
IOUtils.toByteArray(inputJarEntryInputStream)
)
}
inputJarEntryInputStream.close()
}
outputJarOs.closeEntry()
outputJarOs.close()
inputJarFile.close()
return
}
//DIR
//如果不做是否處理,那么僅僅只是將該文件復(fù)制到當(dāng)前Transform的目標(biāo)輸出文件即可
boolean isHandle = doDirectoryAction(inputFile, outputFile)
if (!isHandle) {
//將修改過(guò)的字節(jié)碼copy到dest
FileUtil.copyFileAndMkdirsAsNeed(inputFile, outputFile)
}
}
/**
* 處理Jar文件的資源
*
* 這些都是在工作線程中執(zhí)行的
*
* @param inputStream 上一個(gè)Transform的輸入流
* @param outputStream 當(dāng)前Transform的輸出流
* @return isHandle 是否已經(jīng)處理了該文件,如果已經(jīng)處理了文件返回 true
*
*/
protected abstract boolean doJarAction(InputStream inputStream, OutputStream outputStream)
/**
* 處理目錄的資源文件
*
*
* 這些都是在 工作線程中執(zhí)行的
*
* @param inputJar 上一個(gè)Transform的輸入文件
* @param outputJar 當(dāng)前Transform的輸出文件
* @return 是否已經(jīng)處理了該文件,如果已經(jīng)處理了文件返回 true
*/
protected abstract boolean doDirectoryAction(File inputJar, File outputJar)
}
最后就是你只需要繼承該類(lèi),然后實(shí)現(xiàn)doJarAction和doDirectoryAction方法實(shí)現(xiàn)相應(yīng)功能即可,當(dāng)然這是Groovy版本的。該模本實(shí)現(xiàn)了 增量更新 和 并發(fā)處理 打打提升編譯速度。