??Android Gradle Plugin?
?,簡(jiǎn)稱 AGP,老早之前就想好好研究下Android APK的打包過程,畢竟 ??APK包體積優(yōu)化?
? 的前置知識(shí)之一。
奈何當(dāng)時(shí)的知識(shí)儲(chǔ)備嚴(yán)重不足,硬啃著實(shí)難受,在學(xué)了兩周的Gradle后 ,覺得應(yīng)該有 一戰(zhàn)之力 了,所以這一節(jié)來了!
內(nèi)容較多,建議先收藏,有時(shí)間再慢慢細(xì)品~
0x1、網(wǎng)上流傳的三張APK打包流程圖
??Android官網(wǎng)?? 有一張新的打包流程圖(左),相比起舊的流程圖(右)更抽象,隱藏了很多細(xì)節(jié):
在 ??Android Studio Project Site?? 還找到了一張更詳細(xì)的圖:
如果只是滿足于一個(gè)寫基礎(chǔ)業(yè)務(wù)的Android開發(fā)仔,了解下足矣,不過如果想有更深的造詣,還是建議往下學(xué)的。
比如面試時(shí)(我自己腦補(bǔ)的~):
- 面試官:簡(jiǎn)歷上寫的做過APK體積優(yōu)化?說說都做了哪方面的優(yōu)化:
- 你:從資源大頭入手,把png圖片都轉(zhuǎn)成webp,使用AndResGuard對(duì)資源進(jìn)行混淆;
- 面試官:用Gradle寫一個(gè)webp轉(zhuǎn)換插件,說下思路;
- 你:不會(huì),我一般右鍵直接轉(zhuǎn)換…
- 面試官:那說下AndResGuard的大概原理;
- 你:
本節(jié)就來了解下AGP的構(gòu)建過程,以及簡(jiǎn)單了解下APK的打包過程~
Tips:Tasks那么多,不可能一個(gè)個(gè)去精讀源碼解析,不同插件版本還有差異,不如授之以漁,本文的目的就是讓讀者遇到問題時(shí)懂得如何追根溯源,找到對(duì)應(yīng)的源碼。
0x2、如何查看插件源碼
研究對(duì)象是AGP的源碼,所以要先搞一份源碼,方法有下述幾種:
1. 下載完整源碼
如果磁盤空間比較充足,可以通過repo的方式,將Android Gradle Plugin的源碼下載到本地(貌似30多G):
# 最新源碼的只有3.4.0的
repo init -u https://android.googlesource.com/platform/manifest -b gradle_3.4.0
repo sync
2. 下載部分源碼
當(dāng)然不需要編譯的話,可以直接下對(duì)應(yīng)源碼包,來到下述地址:??build-system??
點(diǎn)tgz下載:
然后用VS Code之類的代碼查看工具查看即可~
3. 取巧(推薦)
在app層級(jí)的build.gradle添加下述依賴:
implementation 'com.android.tools.build:gradle:3.4.0'
build下,然后在左側(cè) ??External Libraries?
? 即可找到源碼:
0x3、閱讀源碼前的一些補(bǔ)充
閱讀源碼前建議溫習(xí)下我前面寫的三篇文章,另外補(bǔ)充點(diǎn)姿勢(shì):
Gradle Plugin 中的Task主要有三種:??普通Task?
?、??增量Task?
?、??Transform?
?。
Task一般會(huì)繼承 ??DefaultTask?
? 或 ??IncrementalTask?
?,而 ??@TaskAction?
? 注解的方法,就是此Task做的事。
繼承 ??IncrementalTask?
? 的類為增量Task,這個(gè)增量是相對(duì)于全量來說的,全量指的是:調(diào)用完clean后第一次編譯過程,修改代碼或資源后再次編譯,就是增量編譯。幾個(gè)關(guān)鍵方法:
public abstract class IncrementalTask extends BaseTask {
// 是否需要增量,默認(rèn)false
@Internal protected boolean isIncremental() { }
// 需要子類實(shí)現(xiàn),全量時(shí)執(zhí)行的任務(wù)
protected abstract void doFullTaskAction() throws Exception;
// 增量時(shí)執(zhí)行的任務(wù),默認(rèn)什么都不執(zhí)行,參數(shù)是增量時(shí)修改過的文件
protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws Exception{ }
@TaskAction
void taskAction(IncrementalTaskInputs inputs) throws Exception {
// 判斷是否是增量,是執(zhí)行doIncrementalTaskAction,否則執(zhí)行doFullTaskAction
}
// 獲取修改文件
private Map<File, FileStatus> getChangedInputs(IncrementalTaskInputs inputs) { }
}
至于 ??Transform?
?(變換),是Android官方提供給開發(fā)者,在**.class → .dex轉(zhuǎn)換期間用來修改.class文件的一套API**,留意 ??transform()?
? 方法的實(shí)現(xiàn)就好。
0x4、執(zhí)行g(shù)radle assemble的Task鏈
我們常常使用下面的命令來打包APK:
gradlew assemble
可以由此入手,看下打包一次都涉及到了哪些Task,鍵入下述命令(linux、mac使用./gradlew):
gradlew assemble --console=plain
輸出結(jié)果及要點(diǎn)簡(jiǎn)述如下所示:
:app:preBuild UP-TO-DATE → 空task,錨點(diǎn)
:app:preDebugBuild → 空task,錨點(diǎn)
:app:compileDebugAidl NO-SOURCE → 處理AIDL
:app:checkDebugManifest → 檢查Manifest是否存在
:app:compileDebugRenderscript NO-SOURCE → 處理renderscript
:app:generateDebugBuildConfig → 生成 BuildConfig.java
:app:mainApkListPersistenceDebug → 生成 app-list.gson
:app:generateDebugResValues → 生成resvalue,generated.xml
:app:generateDebugResources → 空task,錨點(diǎn)
:app:mergeDebugResources → 合并資源文件
:app:createDebugCompatibleScreenManifests → manifest文件中生成compatible-screens,指定屏幕適配
:app:processDebugManifest → 合并manifest.xml文件
:app:processDebugResources → aapt打包資源
:app:compileDebugKotlin → 編譯Kotlin文件
:app:prepareLintJar UP-TO-DATE → 拷貝 lint jar包到指定位置
:app:generateDebugSources → 空task,錨點(diǎn)
:app:javaPreCompileDebug → 生成 annotationProcessors.json 文件
:app:compileDebugJavaWithJavac → 編譯 java文件
:app:compileDebugNdk → 編譯ndk
:app:compileDebugSources → 空task,錨點(diǎn)
:app:mergeDebugShaders → 合并 shader文件
:app:compileDebugShaders → 編譯 shaders
:app:generateDebugAssets → 空task,錨點(diǎn)
:app:mergeDebugAssets → 合并 assests文件
:app:validateSigningDebug → 驗(yàn)證簽名
:app:signingConfigWriterDebug → 編寫SigningConfig信息
:app:checkDebugDuplicateClasses → 檢查重復(fù)class
:app:transformClassesWithDexBuilderForDebug → class打包成dex
:app:transformDexArchiveWithExternalLibsDexMergerForDebug → 打包第三方庫(kù)的dex
:app:transformDexArchiveWithDexMergerForDebug → 打包最終的dex
:app:mergeDebugJniLibFolders → 合并jni lib 文件
:app:transformNativeLibsWithMergeJniLibsForDebug → 合并jnilibs
:app:transformNativeLibsWithStripDebugSymbolForDebug → 去掉native lib里的debug符號(hào)
:app:processDebugJavaRes NO-SOURCE → 處理java res
:app:transformResourcesWithMergeJavaResForDebug → 合并java res
:app:packageDebug → 打包apk
:app:assembleDebug → 空task,錨點(diǎn)
:app:extractProguardFiles → 生成混淆文件
# 還會(huì)打一個(gè)release包,task和上述基本一致,此處省略~
當(dāng)然,也可以直接在 ??Build?
? 窗口直接查看,雙擊右側(cè)Gradle窗口中assemble的Task,然后觀察此窗口:
嘖嘖,還可以看到每個(gè)Task的執(zhí)行時(shí)間,不錯(cuò),但先不跟每個(gè)Task具體內(nèi)容,而是跟下AGP的構(gòu)建過程~
0x5、AGP的構(gòu)建過程
上一節(jié)將Gradle插件時(shí)說過,每個(gè)插件都會(huì)配置一個(gè) ??id名字.properties?
? 的文件,在此寫上插件的實(shí)現(xiàn)類,全局搜定位到下述文件:
打開:
指向:??AppPlugin?
? 類,跟下:
上節(jié)說過:插件類都繼承于 ??Plugin?
?,入口函數(shù) ??apply()?
?,但在這里沒找到,跟下:??AbstractAppPlugin?
? → ??BasePlugin?
?。
① BasePlugin
行吧,在BasePlugin中重寫了 ??apply()?
? 方法,里面調(diào)用了兩個(gè)函數(shù),先跟下:??basePluginApply()?
?
執(zhí)行一些檢查操作,接著是 插件的初始化及配置,而另一個(gè)函數(shù):??pluginSpecificApply()?
? 則是空實(shí)現(xiàn),接著跟下:配置項(xiàng)目、配置擴(kuò)展及創(chuàng)建Tasks的過程。
② configureProject() → 配置項(xiàng)目
創(chuàng)建DataBindingBuilder實(shí)例,強(qiáng)制使用不低于當(dāng)前所支持的最小插件版本,應(yīng)用Java插件,如果啟用了構(gòu)建緩存選項(xiàng),創(chuàng)建buildCache實(shí)例,添加了一個(gè)回調(diào):所有project執(zhí)行完后執(zhí)行資源回收相關(guān)操作。
③ configureExtension() → 配置DSL擴(kuò)展
完成下述幾項(xiàng)工作:
- ① 創(chuàng)建build.gradle中的Android DSL;
- ② 創(chuàng)建VariantFactory、TaskManager、VariantManager實(shí)例;
- ③ 注冊(cè)新增/移除配置的回調(diào),包括:signingConfig,buildType,productFlavor;
- ④ 創(chuàng)建默認(rèn)的debug簽名、debug和release兩種buildType;
④ createTasks() → 創(chuàng)建Tasks
跟下 ??createAndroidTask()?
?:
跟下 ??createAndroidTasks()?
?:
注意下:這里遍歷了所有的variantScope,然后調(diào)用 ??createTasksForVariantData()?
? 創(chuàng)建變體數(shù)據(jù)對(duì)應(yīng)的Tasks:
跟下:??createTasksForVariantScope()?
?:
抽象方法,看下哪里實(shí)現(xiàn)了這個(gè)方法,搜下:??extends TaskManager?
?
最終定位到了:??ApplicationTaskManager?
? 類
噢吼,就是在這里完成APK打包過程的Tasks,可以簡(jiǎn)單跟跟驗(yàn)證下:??createAnchorTasks()?
?,創(chuàng)建錨點(diǎn)Tasks:
跟下:??createVariantPreBuildTask()?
?
2333,跟上面的APK打包Task鏈的相呼應(yīng),AGP插件的構(gòu)建過程就跟到這里,接著了解下APK打包的Task。
0x6、Apk的打包過程
Tips:分享下搜索Task的實(shí)現(xiàn)類的技巧 → 全局搜 “xxx”, “yyy” 即可快速定位對(duì)應(yīng)Task類,如 “compile”, “Aidl”,或者搜索整個(gè)Task,然后刪刪刪匹配。
1. compileDebugAidl
過程簡(jiǎn)述:??將.aidl文件通過aidl工具轉(zhuǎn)換成編譯器能夠處理的Java接口文件?
? 相關(guān)代碼:AidlCompile.java → AidlProcessor.java → call()
2. checkDebugManifest
過程簡(jiǎn)述:??檢查AndroidManifest.xml文件是否存在?
? 相關(guān)代碼:CheckManifest.java
3. compileDebugRenderscript
過程簡(jiǎn)述:??處理Renderscript文件(.rs)?
? 相關(guān)代碼:RenderscriptCompile.java
4. generateDebugBuildConfig
過程簡(jiǎn)述:??生成 BuildConfig.java 文件?
? 相關(guān)代碼:GenerateBuildConfig.java
5. mainApkListPersistenceDebug
過程簡(jiǎn)述:??持久化APK數(shù)據(jù)到apk-list.gson中?
? 相關(guān)代碼:MainApkListPersistence.kt
6. generateDebugResValues
過程簡(jiǎn)述:??遍歷res下的values目錄下xml文件,生成resValues文件generated.xml?
? 相關(guān)代碼:GenerateResValues.java → generate() → ResValueGenerator.java
7. mergeDebugResources
過程簡(jiǎn)述:??使用AAPT2合并資源文件?
? 相關(guān)代碼:MergeResources.doFullTaskAction() → ResourceMerger.mergeData() → MergedResourceWriter.end() → mResourceCompiler.submitCompile() → AaptV2CommandBuilder.makeCompileCommand()
核心源碼解析:
實(shí)現(xiàn)了isIncremental()方法,返回true,說明支持增量編譯,跟下全量編譯方法 ??doFullTaskAction()?
?
ResourcePreprocessor preprocessor = getPreprocessor();
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor)
接著往下走:
繼續(xù):
點(diǎn)進(jìn)merger.mergeData() → ResourceMerger.mergeData() → DataMerger.mergeData()
嘔吼,實(shí)際上調(diào)用的還是 ??MergedResourceWriter?
? 類里的方法,跟下addItem():
不同文件會(huì)創(chuàng)建對(duì)應(yīng)的 ??CompileResourceRequest?
? 實(shí)例,并添加到 ??mCompileResourceRequests?
? 中,后者是一個(gè)ConcurrentLinkedQueue隊(duì)列,資源最后會(huì)在end()方法處處理:
最終調(diào)用 ??AaptV2CommandBuilder.makeCompileCommand()?
? 方法生成aapt2命令去處理資源。
Tips:將圖片轉(zhuǎn)為webp格式的插件一般在此Task前處理~
8. createDebugCompatibleScreenManifests
過程簡(jiǎn)述:??manifest文件中生成compatible-screens,用于屏幕適配?
? 相關(guān)代碼:CompatibleScreensManifest.kt
9. processDebugManifest
過程簡(jiǎn)述:??合并AndroidManifest.xml文件?
? 相關(guān)代碼:ProcessApplicationManifest.java、ProcessLibraryManifest.java
10. processDebugResources
過程簡(jiǎn)述:??調(diào)用aapt2 link 打包資源并生成R.java文件?
? 相關(guān)代碼:TaskManager.java → createProcessResTask()
11. compileDebugKotlin
過程簡(jiǎn)述:??編譯Kotlin文件為字節(jié)碼?
? 相關(guān)代碼:沒找到…可能在kotlin插件源碼里
12. prepareLintJar
過程簡(jiǎn)述:??拷貝lint jar包到指定位置?
? 相關(guān)代碼:PrepareLintJar.java
13. avaPreCompileDebug
過程簡(jiǎn)述:??生成annotationProcessors.json文件?
? 相關(guān)代碼:JavaPreCompileTask.java
14. ompileDebugJavaWithJavac
過程簡(jiǎn)述:??編譯java文件?
? 相關(guān)代碼:AndroidJavaCompile.java
15. compileDebugNdk
過程簡(jiǎn)述:??編譯NDK?
? 相關(guān)代碼:NdkCompile.java
15. mergeDebugShaders
過程簡(jiǎn)述:??合并Renderscript文件(.rs)?
? 相關(guān)代碼:MergeSourceSetFolders.java
16. compileDebugShaders
過程簡(jiǎn)述:??編譯Renderscript文件(.rs)?
? 相關(guān)代碼:ShaderCompile.java
17. mergeDebugAssets
過程簡(jiǎn)述:??合并assets文件?
? 相關(guān)代碼:MergeSourceSetFolders.java
18. validateSigningDebug
過程簡(jiǎn)述:??驗(yàn)證簽名?
? 相關(guān)代碼:ValidateSigningTask.kt 附加信息:檢查當(dāng)前Variant的簽名配置中是否存在密鑰庫(kù)文件,如果當(dāng)前密鑰庫(kù)默認(rèn)為debug keystore,那密鑰庫(kù)不存在也會(huì)進(jìn)行相應(yīng)的創(chuàng)建。
19. signingConfigWriterDebug
過程簡(jiǎn)述:??編寫SigningConfig信息?
? 相關(guān)代碼:SigningConfigWriterTask.kt
20. checkDebugDuplicateClasses
過程簡(jiǎn)述:??檢查重復(fù)class?
? 相關(guān)代碼:CheckDuplicateClassesTask.kt 附加信息:檢查項(xiàng)目外部依賴是否不包含重復(fù)類,打包成dex的時(shí)候再檢測(cè)報(bào)錯(cuò)不怎么友好,所以引入了這個(gè)Task用于快速失敗。
21. transformClassesWithDexBuilderForDebug
過程簡(jiǎn)述:??將class打包成dex?
? 相關(guān)代碼:DexArchiveBuilderTransform.java
核心代碼解析:
定位到 ??transform()?
? 方法,可以看到對(duì)class的處理分為了兩種,目錄下的 class和.jar里的class:
跟下 ??processJarInput()?
?:
繼續(xù)跟:??convertJarToDexArchive()?
?
對(duì)class兩種處理方式,最后都走到 ??convertToDexArchive()?
?,其中調(diào)用了 ??launchProcessing()?
?:
這里的 ??dexArchiveBuilder.convert()?
? 其實(shí)就是內(nèi)部調(diào)用dx或d8來打dex,跟下賦值處:
22. transformDexArchiveWithExternalLibsDexMergerForDebug
過程簡(jiǎn)述:??打包第三方庫(kù)的dex?
? 相關(guān)代碼:ExternalLibsMergerTransform.kt 核心代碼解析:
同樣跟 ??transform()?
?:
創(chuàng)建了一個(gè) ??DexMergerTransformCallable?
? 實(shí)例,然后調(diào) ??call()?
? 方法:
比較簡(jiǎn)單,就是調(diào)下dx或d8將上面生成的依賴庫(kù)的dex合并成一個(gè)dex。
23. transformDexArchiveWithDexMergerForDebug
過程簡(jiǎn)述:??打包最終的dex?
? 相關(guān)代碼:DexMergerTransform.transform() → mergeDex() 核心代碼解析:
跟下 ??submitForMerging()?
? :
也是創(chuàng)建了一個(gè) ??DexMergerTransformCallable?
? 實(shí)例,剩余邏輯同上~
24. mergeDebugJniLibFolders
過程簡(jiǎn)述:??合并jni lib文件?
? 相關(guān)代碼:MergeSourceSetFolders.java
25. transformNativeLibsWithMergeJniLibsForDebug
過程簡(jiǎn)述:??合并jnilibs?
? 相關(guān)代碼:MergeJavaResourcesTransform.java
26. transformNativeLibsWithStripDebugSymbolForDebug
過程簡(jiǎn)述:??去掉native lib里的debug符號(hào)?
? 相關(guān)代碼:StripDebugSymbolTransform.java
27. processDebugJavaRes
過程簡(jiǎn)述:??處理java res?
? 相關(guān)代碼:MergeJavaResourcesTransform.java
28. transformResourcesWithMergeJavaResForDebug
過程簡(jiǎn)述:??合并java res?
? 相關(guān)代碼:MergeJavaResourcesTransform.java
29. packageDebug
過程簡(jiǎn)述:??打包APK?
? 相關(guān)代碼:PackageApplication.java → PackageAndroidArtifact.doTask()
核心代碼如下:
而上面的這些updateXxx()方法,調(diào)用的都是:??IncrementalPackager → updateFiles()?
?
最終調(diào)用mApkCreator.writeZip將上述內(nèi)容寫入到APK中。
30. extractProguardFiles
過程簡(jiǎn)述:??生成混淆文件?
? 相關(guān)代碼:ExtractProguardFiles.java
補(bǔ)充:錨點(diǎn)Task → 空Task
上面的Tasks過濾了錨點(diǎn)Task,啥事錨點(diǎn)Task?答:空Task,用來表明處于某種狀態(tài)。
以 ??preBuild?
? 為例,全局搜它,定位到: ??TaskManager → MAIN_PREBUILD?
?:
跟下引用處:??createTasksBeforeEvaluate()?
?:
注冊(cè)了一個(gè)名為 **??MAIN_PREBUILD?
?**的Task,但沒有傳閉包(任務(wù)內(nèi)容),即空Task。
小結(jié)
以上就是本節(jié)的全部?jī)?nèi)容,看完好像懂了些什么,又說不出來懂了什么,沒關(guān)系,畢竟有點(diǎn)偏理論,為后面Gradle的更深入學(xué)習(xí)及應(yīng)用做鋪墊而已,不急,有疑問或文中有誤地方歡迎評(píng)論區(qū)指出,謝謝~
對(duì)了,喂,三點(diǎn)幾了:
本文摘自 :https://blog.51cto.com/u