Android Gradle Plugin
,簡稱 AGP,老早以前就想好好研究下Android APK的打包過程,畢竟 APK包體積優化
的前置知識之一。java
奈何當時的知識儲備嚴重不足,硬啃着實難受,在學了兩週的Gradle後 ,以爲應該有 一戰之力 了,因此這一節來了!linux
內容較多,建議先收藏,有時間再慢慢細品~android
Android官網 有一張新的打包流程圖(左),相比起舊的流程圖(右)更抽象,隱藏了不少細節:web
在 Android Studio Project Site 還找到了一張更詳細的圖:面試
若是隻是知足於一個寫基礎業務的Android開發仔,瞭解下足矣,不過若是想有更深的造詣,仍是建議往下學的。shell
好比面試時(我本身腦補的~):json
本節就來了解下AGP的構建過程,以及簡單瞭解下APK的打包過程~緩存
Tips:Tasks那麼多,不可能一個個去精讀源碼解析,不一樣插件版本還有差別,不如授之以漁,本文的目的就是讓讀者遇到問題時懂得如何追根溯源,找到對應的源碼。bash
研究對象是AGP的源碼,因此要先搞一份源碼,方法有下述幾種:markdown
若是磁盤空間比較充足,能夠經過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
複製代碼
固然不須要編譯的話,能夠直接下對應源碼包,來到下述地址:build-system
點tgz下載:
而後用VS Code之類的代碼查看工具查看便可~
在app層級的build.gradle添加下述依賴:
implementation 'com.android.tools.build:gradle:3.4.0'
複製代碼
build下,而後在左側 External Libraries
便可找到源碼:
閱讀源碼前建議溫習下我前面寫的三篇文章,另外補充點姿式:
Gradle Plugin 中的Task主要有三種:普通Task
、增量Task
、Transform
。
Task通常會繼承 DefaultTask
或 IncrementalTask
,而 @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()
方法的實現就好。
咱們經常使用下面的命令來打包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的構建過程~
上一節將Gradle插件時說過,每一個插件都會配置一個 id名字.properties
的文件,在此寫上插件的實現類,全局搜定位到下述文件:
打開:
指向:AppPlugin
類,跟下:
上節說過:插件類都繼承於 Plugin
,入口函數 apply()
,但在這裏沒找到,跟下:AbstractAppPlugin
→ BasePlugin
。
行吧,在BasePlugin中重寫了 apply()
方法,裏面調用了兩個函數,先跟下:basePluginApply()
執行一些檢查操做,接着是 插件的初始化及配置,而另外一個函數:pluginSpecificApply()
則是空實現,接着跟下:配置項目、配置擴展及建立Tasks的過程。
建立DataBindingBuilder實例,強制使用不低於當前所支持的最小插件版本,應用Java插件,若是啓用了構建緩存選項,建立buildCache實例,添加了一個回調:全部project執行完後執行資源回收相關操做。
完成下述幾項工做:
跟下 createAndroidTask()
:
跟下 createAndroidTasks()
:
注意下:這裏遍歷了全部的variantScope,而後調用 createTasksForVariantData()
建立變體數據對應的Tasks:
跟下:createTasksForVariantScope()
:
抽象方法,看下哪裏實現了這個方法,搜下:extends TaskManager
最終定位到了:ApplicationTaskManager
類
噢吼,就是在這裏完成APK打包過程的Tasks,能夠簡單跟跟驗證下:createAnchorTasks()
,建立錨點Tasks:
跟下:createVariantPreBuildTask()
2333,跟上面的APK打包Task鏈的相呼應,AGP插件的構建過程就跟到這裏,接着瞭解下APK打包的Task。
Tips:分享下搜索Task的實現類的技巧 → 全局搜 "xxx", "yyy" 便可快速定位對應Task類,如 "compile", "Aidl",或者搜索整個Task,而後刪刪刪匹配。
過程簡述:將.aidl文件經過aidl工具轉換成編譯器可以處理的Java接口文件
相關代碼:AidlCompile.java → AidlProcessor.java → call()
過程簡述:檢查AndroidManifest.xml文件是否存在
相關代碼:CheckManifest.java
過程簡述:處理Renderscript文件(.rs)
相關代碼:RenderscriptCompile.java
過程簡述:生成 BuildConfig.java 文件
相關代碼:GenerateBuildConfig.java
過程簡述:持久化APK數據到apk-list.gson中
相關代碼:MainApkListPersistence.kt
過程簡述:遍歷res下的values目錄下xml文件,生成resValues文件generated.xml
相關代碼:GenerateResValues.java → generate() → ResValueGenerator.java
過程簡述:使用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前處理~
過程簡述:manifest文件中生成compatible-screens,用於屏幕適配
相關代碼:CompatibleScreensManifest.kt
過程簡述:合併AndroidManifest.xml文件
相關代碼:ProcessApplicationManifest.java、ProcessLibraryManifest.java
過程簡述:調用aapt2 link 打包資源並生成R.java文件
相關代碼:TaskManager.java → createProcessResTask()
過程簡述:編譯Kotlin文件爲字節碼
相關代碼:沒找到...可能在kotlin插件源碼裏
過程簡述:拷貝lint jar包到指定位置
相關代碼:PrepareLintJar.java
過程簡述:生成annotationProcessors.json文件
相關代碼:JavaPreCompileTask.java
過程簡述:編譯java文件
相關代碼:AndroidJavaCompile.java
過程簡述:編譯NDK
相關代碼:NdkCompile.java
過程簡述:合併Renderscript文件(.rs)
相關代碼:MergeSourceSetFolders.java
過程簡述:編譯Renderscript文件(.rs)
相關代碼:ShaderCompile.java
過程簡述:合併assets文件
相關代碼:MergeSourceSetFolders.java
過程簡述:驗證簽名
相關代碼:ValidateSigningTask.kt 附加信息:檢查當前Variant的簽名配置中是否存在密鑰庫文件,若是當前密鑰庫默認爲debug keystore,那密鑰庫不存在也會進行相應的建立。
過程簡述:編寫SigningConfig信息
相關代碼:SigningConfigWriterTask.kt
過程簡述:檢查重複class
相關代碼:CheckDuplicateClassesTask.kt 附加信息:檢查項目外部依賴是否不包含重複類,打包成dex的時候再檢測報錯不怎麼友好,因此引入了這個Task用於快速失敗。
過程簡述:將class打包成dex
相關代碼:DexArchiveBuilderTransform.java
核心代碼解析:
定位到 transform()
方法,能夠看到對class的處理分爲了兩種,目錄下的 class和.jar裏的class:
跟下 processJarInput()
:
繼續跟:convertJarToDexArchive()
對class兩種處理方式,最後都走到 convertToDexArchive()
,其中調用了 launchProcessing()
:
這裏的 dexArchiveBuilder.convert()
其實就是內部調用dx或d8來打dex,跟下賦值處:
過程簡述:打包第三方庫的dex
相關代碼:ExternalLibsMergerTransform.kt 核心代碼解析:
一樣跟 transform()
:
建立了一個 DexMergerTransformCallable
實例,而後調 call()
方法:
比較簡單,就是調下dx或d8將上面生成的依賴庫的dex合併成一個dex。
過程簡述:打包最終的dex
相關代碼:DexMergerTransform.transform() → mergeDex() 核心代碼解析:
跟下 submitForMerging()
:
也是建立了一個 DexMergerTransformCallable
實例,剩餘邏輯同上~
過程簡述:合併jni lib文件
相關代碼:MergeSourceSetFolders.java
過程簡述:合併jnilibs
相關代碼:MergeJavaResourcesTransform.java
過程簡述:去掉native lib裏的debug符號
相關代碼:StripDebugSymbolTransform.java
過程簡述:處理java res
相關代碼:MergeJavaResourcesTransform.java
過程簡述:合併java res
相關代碼:MergeJavaResourcesTransform.java
過程簡述:打包APK
相關代碼:PackageApplication.java → PackageAndroidArtifact.doTask()
核心代碼以下:
而上面的這些updateXxx()方法,調用的都是:IncrementalPackager → updateFiles()
最終調用mApkCreator.writeZip將上述內容寫入到APK中。
過程簡述:生成混淆文件
相關代碼:ExtractProguardFiles.java
上面的Tasks過濾了錨點Task,啥事錨點Task?答:空Task,用來代表處於某種狀態。
以 preBuild
爲例,全局搜它,定位到: TaskManager → MAIN_PREBUILD
:
跟下引用處:createTasksBeforeEvaluate()
:
註冊了一個名爲 **MAIN_PREBUILD
**的Task,但沒有傳閉包(任務內容),即空Task。
小結
以上就是本節的所有內容,看無缺像懂了些什麼,又說不出來懂了什麼,不要緊,畢竟有點偏理論,爲後面Gradle的更深刻學習及應用作鋪墊而已,不急,有疑問或文中有誤地方歡迎評論區指出,謝謝~
對了,喂,三點幾了:
參考文獻: