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