🍵補齊Android技能樹——從AGP構建過程到APK打包過程

Android Gradle Plugin,簡稱 AGP,老早以前就想好好研究下Android APK的打包過程,畢竟 APK包體積優化 的前置知識之一。java

奈何當時的知識儲備嚴重不足,硬啃着實難受,在學了兩週的Gradle後 ,以爲應該有 一戰之力 了,因此這一節來了!linux

內容較多,建議先收藏,有時間再慢慢細品~android

0x一、網上流傳的三張APK打包流程圖

Android官網 有一張新的打包流程圖(左),相比起舊的流程圖(右)更抽象,隱藏了不少細節:web

Android Studio Project Site 還找到了一張更詳細的圖:面試

若是隻是知足於一個寫基礎業務的Android開發仔,瞭解下足矣,不過若是想有更深的造詣,仍是建議往下學的。shell

好比面試時(我本身腦補的~):json

  • 面試官:簡歷上寫的作過APK體積優化?說說都作了哪方面的優化:
  • 你:從資源大頭入手,把png圖片都轉成webp,使用AndResGuard對資源進行混淆;
  • 面試官:用Gradle寫一個webp轉換插件,說下思路;
  • 你:不會,我通常右鍵直接轉換...
  • 面試官:那說下AndResGuard的大概原理;
  • 你:

本節就來了解下AGP的構建過程,以及簡單瞭解下APK的打包過程~緩存

Tips:Tasks那麼多,不可能一個個去精讀源碼解析,不一樣插件版本還有差別,不如授之以漁,本文的目的就是讓讀者遇到問題時懂得如何追根溯源,找到對應的源碼。bash


0x二、如何查看插件源碼

研究對象是AGP的源碼,因此要先搞一份源碼,方法有下述幾種:markdown

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下,而後在左側 External Libraries 便可找到源碼:


0x三、閱讀源碼前的一些補充

閱讀源碼前建議溫習下我前面寫的三篇文章,另外補充點姿式:

Gradle Plugin 中的Task主要有三種:普通Task增量TaskTransform

Task通常會繼承 DefaultTaskIncrementalTask,而 @TaskAction 註解的方法,就是此Task作的事。

繼承 IncrementalTask 的類爲增量Task,這個增量是相對於全量來講的,全量指的是:調用完clean後第一次編譯過程,修改代碼或資源後再次編譯,就是增量編譯。幾個關鍵方法:

public abstract class IncrementalTask extends BaseTask {
    // 是否須要增量,默認false
    @Internal protected boolean isIncremental() { }  
    
    // 須要子類實現,全量時執行的任務
    protected abstract void doFullTaskAction() throws Exception;
    
    // 增量時執行的任務,默認什麼都不執行,參數是增量時修改過的文件
    protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws Exception{ }
    
    @TaskAction
    void taskAction(IncrementalTaskInputs inputs) throws Exception {
        // 判斷是不是增量,是執行doIncrementalTaskAction,不然執行doFullTaskAction// 獲取修改文件
    private Map<File, FileStatus> getChangedInputs(IncrementalTaskInputs inputs) { }
}
複製代碼

至於 Transform(變換),是Android官方提供給開發者,在**.class → .dex轉換期間用來修改.class文件的一套API**,留意 transform() 方法的實現就好。


0x四、執行gradle assemble的Task鏈

咱們經常使用下面的命令來打包APK:

gradlew assemble
複製代碼

能夠由此入手,看下打包一次都涉及到了哪些Task,鍵入下述命令(linux、mac使用./gradlew):

gradlew assemble --console=plain
複製代碼

輸出結果及要點簡述以下所示:

: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 窗口直接查看,雙擊右側Gradle窗口中assemble的Task,而後觀察此窗口:

嘖嘖,還能夠看到每一個Task的執行時間,不錯,但先不跟每一個Task具體內容,而是跟下AGP的構建過程~

0x五、AGP的構建過程

上一節將Gradle插件時說過,每一個插件都會配置一個 id名字.properties 的文件,在此寫上插件的實現類,全局搜定位到下述文件:

打開:

指向:AppPlugin 類,跟下:

上節說過:插件類都繼承於 Plugin,入口函數 apply(),但在這裏沒找到,跟下:AbstractAppPluginBasePlugin

① BasePlugin

行吧,在BasePlugin中重寫了 apply() 方法,裏面調用了兩個函數,先跟下:basePluginApply()

執行一些檢查操做,接着是 插件的初始化及配置,而另外一個函數:pluginSpecificApply() 則是空實現,接着跟下:配置項目、配置擴展及建立Tasks的過程。

② configureProject() → 配置項目

建立DataBindingBuilder實例,強制使用不低於當前所支持的最小插件版本,應用Java插件,若是啓用了構建緩存選項,建立buildCache實例,添加了一個回調:全部project執行完後執行資源回收相關操做。

③ configureExtension() → 配置DSL擴展

完成下述幾項工做:

  • ① 建立build.gradle中的Android DSL;
  • ② 建立VariantFactory、TaskManager、VariantManager實例;
  • ③ 註冊新增/移除配置的回調,包括:signingConfig,buildType,productFlavor;
  • ④ 建立默認的debug簽名、debug和release兩種buildType;

④ createTasks() → 建立Tasks

跟下 createAndroidTask()

跟下 createAndroidTasks()

注意下:這裏遍歷了全部的variantScope,而後調用 createTasksForVariantData() 建立變體數據對應的Tasks:

跟下:createTasksForVariantScope()

抽象方法,看下哪裏實現了這個方法,搜下:extends TaskManager

最終定位到了:ApplicationTaskManager

噢吼,就是在這裏完成APK打包過程的Tasks,能夠簡單跟跟驗證下:createAnchorTasks(),建立錨點Tasks:

跟下:createVariantPreBuildTask()

2333,跟上面的APK打包Task鏈的相呼應,AGP插件的構建過程就跟到這裏,接着瞭解下APK打包的Task。

0x六、Apk的打包過程

Tips:分享下搜索Task的實現類的技巧 → 全局搜 "xxx", "yyy" 便可快速定位對應Task類,如 "compile", "Aidl",或者搜索整個Task,而後刪刪刪匹配。

1. compileDebugAidl

過程簡述:將.aidl文件經過aidl工具轉換成編譯器可以處理的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數據到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()

核心源碼解析:

實現了isIncremental()方法,返回true,說明支持增量編譯,跟下全量編譯方法 doFullTaskAction()

ResourcePreprocessor preprocessor = getPreprocessor();
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor)
複製代碼

接着往下走:

繼續:

點進merger.mergeData() → ResourceMerger.mergeData() → DataMerger.mergeData()

嘔吼,實際上調用的仍是 MergedResourceWriter 類裏的方法,跟下addItem():

不一樣文件會建立對應的 CompileResourceRequest 實例,並添加到 mCompileResourceRequests 中,後者是一個ConcurrentLinkedQueue隊列,資源最後會在end()方法到處理:

最終調用 AaptV2CommandBuilder.makeCompileCommand() 方法生成aapt2命令去處理資源。

Tips:將圖片轉爲webp格式的插件通常在此Task前處理~


8. createDebugCompatibleScreenManifests

過程簡述:manifest文件中生成compatible-screens,用於屏幕適配 相關代碼:CompatibleScreensManifest.kt


9. processDebugManifest

過程簡述:合併AndroidManifest.xml文件 相關代碼:ProcessApplicationManifest.java、ProcessLibraryManifest.java


10. processDebugResources

過程簡述:調用aapt2 link 打包資源並生成R.java文件 相關代碼:TaskManager.java → createProcessResTask()


11. compileDebugKotlin

過程簡述:編譯Kotlin文件爲字節碼 相關代碼:沒找到...可能在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,那密鑰庫不存在也會進行相應的建立。


19. signingConfigWriterDebug

過程簡述:編寫SigningConfig信息 相關代碼:SigningConfigWriterTask.kt


20. checkDebugDuplicateClasses

過程簡述:檢查重複class 相關代碼:CheckDuplicateClassesTask.kt 附加信息:檢查項目外部依賴是否不包含重複類,打包成dex的時候再檢測報錯不怎麼友好,因此引入了這個Task用於快速失敗。


21. transformClassesWithDexBuilderForDebug

過程簡述:將class打包成dex 相關代碼:DexArchiveBuilderTransform.java

核心代碼解析:

定位到 transform() 方法,能夠看到對class的處理分爲了兩種,目錄下的 class和.jar裏的class:

跟下 processJarInput()

繼續跟:convertJarToDexArchive()

對class兩種處理方式,最後都走到 convertToDexArchive(),其中調用了 launchProcessing()

這裏的 dexArchiveBuilder.convert() 其實就是內部調用dx或d8來打dex,跟下賦值處:


22. transformDexArchiveWithExternalLibsDexMergerForDebug

過程簡述:打包第三方庫的dex 相關代碼:ExternalLibsMergerTransform.kt 核心代碼解析:

一樣跟 transform()

建立了一個 DexMergerTransformCallable 實例,而後調 call() 方法:

比較簡單,就是調下dx或d8將上面生成的依賴庫的dex合併成一個dex。


23. transformDexArchiveWithDexMergerForDebug

過程簡述:打包最終的dex 相關代碼:DexMergerTransform.transform() → mergeDex() 核心代碼解析:

跟下 submitForMerging()

也是建立了一個 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()方法,調用的都是:IncrementalPackager → updateFiles()

最終調用mApkCreator.writeZip將上述內容寫入到APK中。


30. extractProguardFiles

過程簡述:生成混淆文件 相關代碼:ExtractProguardFiles.java


補充:錨點Task → 空Task

上面的Tasks過濾了錨點Task,啥事錨點Task?答:空Task,用來代表處於某種狀態

preBuild 爲例,全局搜它,定位到: TaskManager → MAIN_PREBUILD

跟下引用處:createTasksBeforeEvaluate()

註冊了一個名爲 **MAIN_PREBUILD**的Task,但沒有傳閉包(任務內容),即空Task。


小結

以上就是本節的所有內容,看無缺像懂了些什麼,又說不出來懂了什麼,不要緊,畢竟有點偏理論,爲後面Gradle的更深刻學習及應用作鋪墊而已,不急,有疑問或文中有誤地方歡迎評論區指出,謝謝~

對了,喂,三點幾了:


參考文獻

相關文章
相關標籤/搜索