通過 Gradle進階計劃(一)Gradle初探 的介紹,我們已經對Gradle有了初步的了解,這篇文章我們更深入研究一下 Gradle Plugin 的原理,
一、Gradle 和 Gradle Plugin
首先,我們需要先明確一個概念,就是 Gradle 和 Gradle Plugin 是不同的,
(一)Gradle
結合上一篇文章,官方已經對Gradle已經有了很詳細的定義,這里在重點解釋一下,
Gradle 是一個構建專案的工具,將它用來編譯Android App,能夠簡化你的編譯、打包、測驗程序,它其實不僅僅是用在Android Studio上,如何去理解構建工具呢?構建工具就是對你的專案進行編譯、運行、簽名、打包、依賴管理等一系列功能的合集,例如 Eclipse 最初是用來做 Java 開發的,Google 為了能在 Eclipse 上進行Android 開發,開發了ADT 插件(Android Developer Tools),正是因為有了 ADT ,我們才可以在 Eclipse 上進行編譯、運行、簽名、打包等一系列流程,而這背后的作業都是 ADT 的功勞,ADT 就是我們的構建工具,一般來說,構建工具除了以上提到的編譯、運行、簽名、打包等,還具備依賴管理的功能,什么是依賴管理呢?例如我們以前在 Eclipse 上開發 Android 參考第三方庫,一般都是先下載 jar 檔案,然后把 jar 檔案添加到 libs 目錄,依賴管理就是將這個程序自動化,
在 Gradle 之前也是 Android 也有其他構建工具的,這就是大名鼎鼎的 Maven,Java 世界中主要有三大構建工具:Ant、Maven 和 Gradle,經過幾年的發展,Ant 幾乎銷聲匿跡,所以在早期 Android 開發中,是使用 Maven 作為構建工具的,但是 Gradle 相較于 Maven 有著較大的優勢,所以 AS 時代,Google 用 Gradle 替代了 Maven 作為 Android 開發的構建工具,
- Gradle 對于依賴的管理更加的簡潔,不需要像 Maven 定義 groupId、artifactId、version 等繁瑣的屬性值;
- Gradle 支持動態的版本依賴,在版本號后面使用+號的方式可以實作動態的版本管理;
- Gradle 在解決依賴沖突方面實作機制更加明確,使用 Maven 和 Gradle 進行依賴管理時都采用的是傳遞性依賴;而如果多個依賴項指向同一個依賴項的不同版本時就會引起依賴沖突,而 Maven 處理這種依賴關系往往是噩夢一般的存在;
- Gradle 多模塊構建方面具有優勢,相較于 Maven 聚合 POM 的形式,Gradle allprojects 和 subprojects 的定義方法更加的方便和清晰;
- Gradle 在插件機制的支持上做的更好,Maven 配置語法太受限于XML,Gradle中則一切變得非常簡單,
(二)Gradle Plugin
為了支持 Gradle 能在 AS 上使用,Google 開發了一個 AS 的插件叫 Android Gradle Plugin ,所以我們能在 AS 上使用 Gradle 完全是因為這個插件的原因,它一邊呼叫 Gradle 本身的代碼和批處理工具來構建專案,一邊呼叫 Android SDK 的編譯、打包功能,從而讓我們能夠順暢地在AS上進行開發,
Gradle Plugin 是獨立于Android Studio 運行的,它的更新也是與Android Studio分開的,Gradle Plugin 會有版本號,每個版本號又對應有一個或一些 Gradle發行版本(一般是限定一個最低版本),這也是為什么如果這兩個版本對應不上了,你的工程構建的時候會報錯,
| 插件版本 | Gradle版本 |
|---|---|
| 1.0.0 - 1.1.3 | 2.2.1 - 2.3 |
| 1.2.0 - 1.3.1 | 2.2.1 - 2.9 |
| 1.5.0 | 2.2.1 - 2.13 |
| 2.0.0 - 2.1.2 | 2.10 - 2.13 |
| 2.1.3 - 2.2.3 | 2.14.1+ |
| 2.3.0+ | 3.3+ |
| 3.0.0+ | 4.1+ |
| 3.1.0+ | 4.4+ |
Android Studio 3.0 之后自動將插件版本升級到3.0.0,所以我們也需要對應地把Gradle升級到4.1才行,
其實前面我們還提到了Gradle Wrapper,這里再簡單說一下,
Gradle Wrapper:意為 Gradle 的包裝,它的作用是簡化Gradle本身的安裝、部署,不同版本的專案可能需要不同版本的Gradle,手工部署的話比較麻煩,而且可能產生沖突,所以需要Gradle Wrapper幫你搞定這些事情,Gradle Wrapper是Gradle專案的一部分,
假設我們本地有多個專案,一個是比較老的專案,還用著 Gradle 1.0 的版本,一個是比較新的專案用了 Gradle 2.0 的版本,但是你兩個專案肯定都想要同時運行的,如果你只裝了 Gradle 1.0 的話那肯定不行,所以為了解決這個問題,Google 推出了 Gradle Wrapper 的概念,它在你每個專案都配置了一個指定版本的 Gradle ,你可以理解為每個 Android 專案本地都有一個小型的 Gradle ,通過這個每個專案你可以支持用不同的 Gradle 版本來構建專案,
總結:
Gradle:是一個構建工具,版本定義在 gradle-wrapper.properties中的distributionUrl=https/://services.gradle.org/distributions/gradle-x.xx-all.zip
Gradle Plugin:是谷歌為使用Gradle而自行開發的工具,版本定義在 build.gradle中依賴的classpath 'com.android.tools.build:gradle:x.x.x'
二、Gradle Plugin 主要流程
對于原始碼的分析這里就不展開講了,詳情可參照 android gradle plugin 原始碼地址,我只介紹一下涉及到的主要流程,
Android gradle plugin 的入口類 com.android.build.gradle.AppPlugin,這個可以查看 properties 檔案,里面宣告了對應插件的入口類,
AppPlugin 繼承自 BasePlugin,AppPlugin 里沒有做太多的操作,主要是重寫了 createTaskManager 和 createExtension,剩下的大部分作業還是在 BasePlugin 里做的,

有幾點需要注意:
- build.gradle 里見到的 android {} dsl 是在 BasePlugin.configureExtension() 里宣告的
- 主要的 task 是在 BasePlugin.createAndroidTasks() 里生成的
- 主要 task 的實作可以在 TaskManager 中找到
- transform 會轉化成 TransformTask
三、主要 Task 分析
我們先看一下,生成一個 APK 所需的構建流程是怎樣的,官方流程圖如下:

下面是詳細的打包流程圖:

(1)打包資源檔案,使用aapt來打包res資源檔案,生成R.java、resources.arsc和res檔案,R.java檔案是所有res資源的id串列,resources.arsc里面會對所有的資源id進行組裝,在apk運行時獲取資源的時候會根據設備的情況獲得不同的資源,
(2)處理aidl檔案,生成相應java 檔案,這個階段aidl會處理.aidl檔案,生成對應的Java介面檔案,對于沒有使用到aidl的android工程,可以跳過此步驟,
(3)編譯工程源代碼,生成相應class 檔案,通過javac編譯R.java、Java介面檔案、Java源檔案,生成.class檔案,如果有配置混淆的話,會編譯成混淆的class檔案,
(4)轉換所有class檔案,生成classes.dex檔案,Android系統的Dalvik虛擬機的可執行檔案為DEX格式,程式運行所需的class.dex就是在這一步生成的,使用到的工具為dx,主要的作業是將java位元組碼轉換為Dalvik位元組碼、壓縮常量池、消除冗余資訊等,
(5)打包生成apk,apkbuilder將classes.dex、resources.arsc、res檔案夾(res/raw資源被原裝不動地打包進APK之外,其它的資源都會被編譯或者處理)、Other Resources(assets檔案夾)、AndroidManifest.xml打包成apk檔案,
(6)對apk檔案進行簽名,通過jarsigner對apk進行簽名,
(7)對簽名后的apk檔案進行對其處理,通過zipalign對簽名后的apk進行對齊處理,它能夠對打包后的app進行優化,
那么以 Task 的維度來看 apk 的打包,是什么流程呢?通過執行下面的命令,可以列印出打包一個 apk 需要哪些 task ,
./gradlew android-gradle-plugin-source:assembleDebug --console=plain
輸出結果如下:
:android-gradle-plugin-source:preBuild UP-TO-DATE
:android-gradle-plugin-source:preDebugBuild
:android-gradle-plugin-source:compileDebugAidl
:android-gradle-plugin-source:compileDebugRenderscript
:android-gradle-plugin-source:checkDebugManifest
:android-gradle-plugin-source:generateDebugBuildConfig
:android-gradle-plugin-source:prepareLintJar UP-TO-DATE
:android-gradle-plugin-source:generateDebugResValues
:android-gradle-plugin-source:generateDebugResources
:android-gradle-plugin-source:mergeDebugResources
:android-gradle-plugin-source:createDebugCompatibleScreenManifests
:android-gradle-plugin-source:processDebugManifest
:android-gradle-plugin-source:splitsDiscoveryTaskDebug
:android-gradle-plugin-source:processDebugResources
:android-gradle-plugin-source:generateDebugSources
:android-gradle-plugin-source:javaPreCompileDebug
:android-gradle-plugin-source:compileDebugJavaWithJavac
:android-gradle-plugin-source:compileDebugNdk NO-SOURCE
:android-gradle-plugin-source:compileDebugSources
:android-gradle-plugin-source:mergeDebugShaders
:android-gradle-plugin-source:compileDebugShaders
:android-gradle-plugin-source:generateDebugAssets
:android-gradle-plugin-source:mergeDebugAssets
:android-gradle-plugin-source:transformClassesWithDexBuilderForDebug
:android-gradle-plugin-source:transformDexArchiveWithExternalLibsDexMergerForDebug
:android-gradle-plugin-source:transformDexArchiveWithDexMergerForDebug
:android-gradle-plugin-source:mergeDebugJniLibFolders
:android-gradle-plugin-source:transformNativeLibsWithMergeJniLibsForDebug
:android-gradle-plugin-source:transformNativeLibsWithStripDebugSymbolForDebug
:android-gradle-plugin-source:processDebugJavaRes NO-SOURCE
:android-gradle-plugin-source:transformResourcesWithMergeJavaResForDebug
:android-gradle-plugin-source:validateSigningDebug
:android-gradle-plugin-source:packageDebug
:android-gradle-plugin-source:assembleDebug
下面列出了各個 task 的實作類及作用:
| Task | 對應實作類 | 作用 |
|---|---|---|
| preBuild | 空 task,只做錨點使用 | |
| preDebugBuild | 空 task,只做錨點使用,與 preBuild 區別是這個 task 是 variant 的錨點 | |
| compileDebugAidl | AidlCompile | 處理 aidl |
| compileDebugRenderscript | RenderscriptCompile | 處理 renderscript |
| checkDebugManifest | CheckManifest | 檢測 manifest 是否存在 |
| generateDebugBuildConfig | GenerateBuildConfig | 生成 BuildConfig.java |
| prepareLintJar | PrepareLintJar | 拷貝 lint jar 包到指定位置 |
| generateDebugResValues | GenerateResValues | 生成 resvalues,generated.xml |
| generateDebugResources | 空 task,錨點 | |
| mergeDebugResources | MergeResources | 合并資源檔案 |
| createDebugCompatibleScreenManifests | CompatibleScreensManifest | manifest 檔案中生成 compatible-screens,指定螢屏適配 |
| processDebugManifest | MergeManifests | 合并 manifest 檔案 |
| splitsDiscoveryTaskDebug | SplitsDiscovery | 生成 split-list.json,用于 apk 分包 |
| processDebugResources | ProcessAndroidResources | aapt 打包資源 |
| generateDebugSources | 空 task,錨點 | |
| javaPreCompileDebug | JavaPreCompileTask | 生成 annotationProcessors.json 檔案 |
| compileDebugJavaWithJavac | AndroidJavaCompile | 編譯 java 檔案 |
| compileDebugNdk | NdkCompile | 編譯 ndk |
| compileDebugSources | 空 task,錨點使用 | |
| mergeDebugShaders | MergeSourceSetFolders | 合并 shader 檔案 |
| compileDebugShaders | ShaderCompile | 編譯 shaders |
| generateDebugAssets | 空 task,錨點 | |
| mergeDebugAssets | MergeSourceSetFolders | 合并 assets 檔案 |
| transformClassesWithDexBuilderForDebug | DexArchiveBuilderTransform | class 打包 dex |
| transformDexArchiveWithExternalLibsDexMergerForDebug | ExternalLibsMergerTransform | 打包三方庫的 dex,在 dex 增量的時候就不需要再 merge 了,節省時間 |
| transformDexArchiveWithDexMergerForDebug | DexMergerTransform | 打包最終的 dex |
| mergeDebugJniLibFolders | MergeSouceSetFolders | 合并 jni lib 檔案 |
| transformNativeLibsWithMergeJniLibsForDebug | MergeJavaResourcesTransform | 合并 jnilibs |
| transformNativeLibsWithStripDebugSymbolForDebug | StripDebugSymbolTransform | 去掉 native lib 里的 debug 符號 |
| processDebugJavaRes | ProcessJavaResConfigAction | 處理 java res |
| transformResourcesWithMergeJavaResForDebug | MergeJavaResourcesTransform | 合并 java res |
| validateSigningDebug | ValidateSigningTask | 驗證簽名 |
| packageDebug | PackageApplication | 打包 apk |
| assembleDebug | 空 task,錨點 |
以上就是打包一個 APK 所需要的 Task,在分析主要Task之前,我們先了解一下 Task 的分類,
在 Gradle Plugin 中的 Task 主要有三種:
(1)普通 Task:
- 一般繼承 DefaultTask;
- 看 @TaskAction 注解的方法,此方法就是這個 Task 做的事情
(2)增量 Task:相對于全量來說的,全量我們可以理解為呼叫 clean 以后第一次編譯的程序,這個就是全量編譯,之后修改了代碼或者資源檔案,再次編譯,就是增量編譯,
- 首先這個 Task 要繼承 IncrementalTask;
- 其次看 isIncremental 方法,如果回傳 true,說明支持增量,回傳 false 則不支持;
- 然后看 doFullTaskAction 方法,是全量的時候執行的操作;
- 最后看 doIncrementalTaskAction 方法,這里是增量的時候執行的操作,
(3)Transform:這個后面我會專門出一篇文章來講,
- 繼承自 Transform;
- 重點關注 transform 方法實作,
1. generateDebugBuildConfig
(1)實作類:GenerateBuildConfig
(2)整體實作:

(3)代碼呼叫:
GenerateBuildConfig.generate -> BuildConfigGenerator.generate -> JavaWriter
(4)分析實作:
在 GenerateBuildConfig 中,主要生成代碼的步驟如下:
- 生成 BuildConfigGenerator
- 添加默認的屬性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME
- 添加自定義屬性
- 呼叫 JavaWriter 生成 BuildConfig.java 檔案
2. mergeDebugResources
(1)實作類:MergeResources
(2)整體實作:

(3)代碼呼叫:
MergeResources.doFullTaskAction -> ResourceMerger.mergeData -> MergedResourceWriter.end
-> QueueableAapt2.compile -> Aapt2QueuedResourceProcessor.compile -> AaptProcess.compile
-> AaptV2CommandBuilder.makeCompile
(4)分析實作:
MergeResources 這個類,繼承自 IncrementalTask,我們重點關注 doFullTaskAction 這個全量方法,
- 通過 getConfiguredResourceSets() 獲取 resourceSets,包括了自己的 res/ 和 依賴庫的 res/ 以及 build/generated/res/rs
- 創建 ResourceMerger
- 創建 QueueableResourceCompiler,因為 gradle3.x 以后支持了 aapt2,所以這里有兩種選擇 aapt 和 aapt2,其中 aapt2 有三種模式,OutOfProcessAaptV2,AaptV2Jni,QueueableAapt2,這里默認創建了 QueueableAapt2,resourceCompiler = QueueableAapt2
- 將第一步獲取的 resourceSet 加入 ResourceMerger 中
- 創建 MergedResourceWriter
- 呼叫 ResourceMerger.mergeData 合并資源
- 呼叫 MergedResourceWriter 的 start(),addItem(),end() 方法
- 呼叫 QueueableAapt2 -> Aapt2QueuedResourceProcessor -> AaptProcess 處理資源,這一步呼叫 aapt2 命令去處理資源,處理完以后 xxx.xml.flat 格式
3. processDebugResources
(1)實作類:ProcessAndroidResources
(2)整體實作:

(3)代碼呼叫:
ProcessAndroidResources.doFullTaskAction -> ProcessAndroidResources.invokeAaptForSplit
-> AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link
-> AaptProcess.link -> AaptV2CommandBuilder.makeLink
(4)分析實作:
- 獲取 split 資料,回傳的是一個 ApkData 串列,ApkData 有三個子類,分別是 Main,Universal,FullSplit,這里的 ApkData 會回傳一個 Universal 和多個 FullSplit,Universal 代表的是主 apk,FullSplit 就是根據螢屏密度拆分的 apk,如果我們沒有配置 splits apk,那么這里只會回傳一個 Main 的實體,標識完整的 apk,
- 處理 main 和 不依賴 density 的 ApkData 資源
- 呼叫 invokeAaptForSplit 處理資源
- 呼叫 AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink 處理資源,生成資源包以及 R.java 檔案
- 處理其他 ApkData 資源,這里只會生成資源包而不會生成 R.java 檔案
4. processDebugManifest
(1)實作類:MergeManifests
(2)整體實作:

(3)代碼呼叫:
MergeManifests.dofFullTaskAction -> AndroidBuilder.mergeManifestsForApplication
-> Invoker.merge -> ManifestMerge2.merge
(4)分析實作:
這個 task 功能主要是合并 mainfest,包括 module 和 flavor 里的,整個程序通過 MergingReport,ManifestMerger2 和 XmlDocument 進行,
這里直接看 ManifestMerger2.merge() 的 merge 程序 , 主要有幾個步驟:
- 獲取依賴庫的 manifest 資訊,用 LoadedManifestInfo 標識
- 獲取主 module 的 manifest 資訊
- 替換主 module 的 Manifest 中定義的某些屬性,替換成 gradle 中定義的屬性 例如: package, version_code, version_name, min_sdk_versin 等
- 合并 flavor,buildType 中的 manifest
- 合并依賴庫的 manifest
- 處理 manifest 的 placeholders
- 之后對最終合并后的 manifest 中的一些屬性重新進行一次替換,類似步驟 4
- 保存 manifest 到 build/intermediates/manifest/fullxxx/AndroidManifest.xml 這就生成了最終的 Manifest 檔案
5. transformClassesWithDexBuilderForDebug
(1)實作類:DexArchiveBuilderTransform
(2)整體實作:

(3)代碼呼叫:
DexArchiveBuilderTransform.transform -> DexArchiveBuilderTransform.convertJarToDexArchive
-> DexArchiveBuilderTransform.convertToDexArchive -> DexArchiveBuilderTransform.launchProcessing
-> DxDexArchiveBuilder.convert
(4)分析實作:
在 DexArchiveBuilderTransform 中,對 class 的處理分為兩種方式,一種是對 目錄下的 class 進行處理,一種是對 .jar 里的 class 進行處理,
為什么要分為這兩種方式呢?.jar 中的 class 一般來說都是依賴庫,基本上不會改變,gradle 在這里做了一個快取,但是兩種方式最終都會呼叫到 convertToDexArchive,
- convertJarToDexArchive 處理 jar,處理 .jar 時,會對 jar 包中的每一個 class 都單獨打成一個 .dex 檔案,之后還是放在 .jar 包中
- convertToDexArchive 處理 dir 以及 jar 的后續處理, 對 dir 處理使用 convertToDexArchive,其中會呼叫 launchProcessing,在 launchProcessing 中,有下面幾個步驟:a)判斷目錄下的 class 是否新增或者修改過 b)呼叫 DexArchiveBuilder.build 去處理修改過的 class c)DexArchiveBuilder 有兩個子類,D8DexArchiveBuilder 和 DxDexArchiveBuilder,分別是呼叫 d8 和 dx 去打 dex
6. transformDexArchiveWithExternalLibsDexMergerForDebug / transformDexArchiveWithDexMergerForDebug
(1)實作類:ExternalLibsMergerTransform / DexMergerTransform
(2)整體實作:

(3)代碼呼叫:
// dx
ExternalLibsMergerTransform / DexMergerTransform.transform -> DexMergerTransformCallable.call
-> DxDexArchiveMerger.mergeDexArchives -> DxDexArchiveMerger.mergeMonoDex
-> DexArchiveMergerCallable.call -> DexMerger.merge
// d8
ExternalLibsMergerTransform / DexMergerTransform.transform -> DexMergerTransformCallable.call
-> D8DexArchiveMerger.mergeDexArchives -> 呼叫 D8 命令
(4)分析實作:
主要是處理依賴庫的 dex,把上一步生成的依賴庫的 dex merge 成一個 dex,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/255249.html
標籤:其他
上一篇:Android學習記錄(二十一)
