【Android 修煉手冊】Gradle 篇 -- Android Gradle Plugin 主要 Task 分析
預備知識
- 理解 gradle 的基本開發
- 了解 gradle task 和 plugin 使用及開發
- 了解 android gradle plugin 的使用
看完本文可以達到什么程度
- 了解 android gradle plugin 中各個 task 作用
- 了解 android gradle plugin 中主要 task 的實現
閱讀前准備工作
1.項目添加 android gradle plugin 依賴
compile 'com.android.tools.build:gradle:3.0.1'
通過這種方式,可以直接依賴 plugin 的源碼,讀起來比較方便
2.官方對照源碼地址 android gradle plugin 源碼地址
大家可以直接 clone EasyGradle 項目,把 app/build.gradle 里的 implementation 'com.android.tools.build:gradle:3.0.1' 注釋打開就可以了。
在 Gradle的基本使用 和 Android Gradle Plugin 主要流程分析 里,我們知道了 gradle 中 task 的重要性,以及 android gradle plugin 的主要流程,這一篇就來分析一下 android gradle plugin 中一些重要的 task 是怎么執行的。
一、Android 打包流程
在介紹 Android Gradle Plugin Task 之前,我們先看看一個 apk 的構建流程,先放一張官方流程圖:

官方介紹的流程如下:
- 編譯器將您的源代碼轉換成 DEX(Dalvik Executable) 文件(其中包括 Android 設備上運行的字節碼),將所有其他內容轉換成已編譯資源。
- APK 打包器將 DEX 文件和已編譯資源合並成單個 APK。 不過,必須先簽署 APK,才能將應用安裝並部署到 Android 設備上。
- APK 打包器使用調試或發布密鑰庫簽署您的 APK:
- 在生成最終 APK 之前,打包器會使用 zipalign 工具對應用進行優化,減少其在設備上運行時占用的內存。
那么以 Task 的維度來看 apk 的打包,是什么流程呢?我們先執行下面的命令,看一下打包一個 apk 需要哪些 task
首先我們看一下 打包一個 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
上面就是打包一個 apk 需要的 task
二、Task 對應實現類
我們先看看每個 task 都是做什么的,以及其對應的實現類。
先回憶一下,我們在前面 android-gradle-plugin 主要流程分析里說到過,task 的實現可以在 TaskManager 里找到,創建 task 的方法主要是兩個,TaskManager.createTasksBeforeEvaluate() 和 ApplicationTaskManager.createTasksForVariantScope(),所以這些 task 的實現,也在這兩個類里找就可以,下面列出了各個 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,錨點 |
三、如何去讀 Task 的代碼
在 gradle plugin 中的 Task 主要有三種,一種是普通的 task,一種是增量 task,一種是 transform,下面分別看下這三種 task 怎么去讀。
如何讀 Task 的代碼
- 看 Task 繼承的父類,一般來說,會繼承 DefaultTask,IncrementalTask
- 看 @TaskAction 注解的方法,此方法就是這個 Task 做的事情
如何讀 IncrementalTask
我們先看看下這個類,這個類表示的是增量 Task,什么是增量呢?是相對於 全量來說的,全量我們可以理解為調用 clean 以后第一次編譯的過程,這個就是全量編譯,之后修改了代碼或者資源文件,再次編譯,就是增量編譯。
其中比較重要的幾個方法如下:
public abstract class IncrementalTask extends BaseTask { // ... @Internal protected boolean isIncremental() { // 是否需要增量,默認是 false return false; } // 需要子類實現,全量的時候執行的任務 protected abstract void doFullTaskAction() throws Exception; // 增量的時候執行的任務,默認是什么都不執行,參數是增量的時候修改過的文件 protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws Exception { } @TaskAction void taskAction(IncrementalTaskInputs inputs) throws Exception { // 判斷是否是增量 if(this.isIncremental() && inputs.isIncremental()) { this.doIncrementalTaskAction(this.getChangedInputs(inputs)); } else { this.getProject().getLogger().info("Unable do incremental execution: full task run"); this.doFullTaskAction(); } } // 獲取修改的文件 private Map<File, FileStatus> getChangedInputs(IncrementalTaskInputs inputs) { Map<File, FileStatus> changedInputs = Maps.newHashMap(); inputs.outOfDate((change) -> { FileStatus status = change.isAdded()?FileStatus.NEW:FileStatus.CHANGED; changedInputs.put(change.getFile(), status); }); inputs.removed((change) -> { FileStatus var10000 = (FileStatus)changedInputs.put(change.getFile(), FileStatus.REMOVED); }); return changedInputs; } }
簡單介紹了 IncrementalTask 之后,我們這里強調一下,如何去讀一個 增量 Task 的代碼,主要有四步:
- 首先這個 Task 要繼承 IncrementalTask,
- 其次看 isIncremental 方法,如果返回 true,說明支持增量,返回 false 則不支持
- 然后看 doFullTaskAction 方法,是全量的時候執行的操作
- 最后看 doIncrementalTaskAction 方法,這里是增量的時候執行的操作
如何讀 Transform
- 繼承自 Transform
- 看其 transform 方法的實現
四、重點 Task 實現分析
上面每個 task 已經簡單說明了具體做什么以及對應的實現類,下面選了幾個比較重要的來分析一下其實現
為什么分析這幾個呢?這幾個代表了 gradle 自動生成代碼,資源的處理,以及 dex 的處理,算是 apk 打包過程中比較重要的幾環。
generateDebugBuildConfig
processDebugManifest
mergeDebugResources
processDebugResources
transformClassesWithDexBuilderForDebug
transformDexArchiveWithExternalLibsDexMergerForDebug
transformDexArchiveWithDexMergerForDebug
分析過程主要下面幾個步驟,實現類,整體實現圖,調用鏈路(方便以后回看代碼),以及重要代碼分析
4.1 generateDebugBuildConfig
4.1.1 實現類
GenerateBuildConfig
4.1.2 整體實現圖

4.1.3 代碼調用鏈路
GenerateBuildConfig.generate -> BuildConfigGenerator.generate -> JavaWriter
4.1.4 主要代碼分析
在 GenerateBuildConfig 中,主要生成代碼的步驟如下:
- 生成 BuildConfigGenerator
- 添加默認的屬性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME
- 添加自定義屬性
- 調用 JavaWriter 生成 BuildConfig.java 文件
// GenerateBuildConfig.generate() @TaskAction void generate() throws IOException { // ... BuildConfigGenerator generator = new BuildConfigGenerator( getSourceOutputDir(), getBuildConfigPackageName()); // 添加默認的屬性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME generator .addField( "boolean", "DEBUG", isDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false") .addField("String", "APPLICATION_ID", '"' + appPackageName.get() + '"') .addField("String", "BUILD_TYPE", '"' + getBuildTypeName() + '"') .addField("String", "FLAVOR", '"' + getFlavorName() + '"') .addField("int", "VERSION_CODE", Integer.toString(getVersionCode())) .addField( "String", "VERSION_NAME", '"' + Strings.nullToEmpty(getVersionName()) + '"') .addItems(getItems()); // 添加自定義屬性 List<String> flavors = getFlavorNamesWithDimensionNames(); int count = flavors.size(); if (count > 1) { for (int i = 0; i < count; i += 2) { generator.addField( "String", "FLAVOR_" + flavors.get(i + 1), '"' + flavors.get(i) + '"'); } } // 內部調用 JavaWriter 生成 java 文件 generator.generate(); }
4.2 mergeDebugResources
4.2.1 實現類
MergeResources
4.2.2 整體實現圖
4.2.3 調用鏈路
MergeResources.doFullTaskAction -> ResourceMerger.mergeData -> MergedResourceWriter.end -> QueueableAapt2.compile -> Aapt2QueuedResourceProcessor.compile -> AaptProcess.compile -> AaptV2CommandBuilder.makeCompile
4.2.4 主要代碼分析
MergeResources 這個類,繼承自 IncrementalTask,按照前面說的閱讀增量 Task 代碼的步驟,依次看三個方法的實現:isIncremental,doFullTaskAction,doIncrementalTaskAction
- isIncremental
// 說明 Task 支持增量 protected boolean isIncremental() { return true; }
- doFullTaskAction
- 通過 getConfiguredResourceSets() 獲取 resourceSets,包括了自己的 res/ 和 依賴庫的 res/ 以及 build/generated/res/rs
// MergeResources.doFullTaskAction() List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
- 創建 ResourceMerger
// MergeResources.doFullTaskAction() ResourceMerger merger = new ResourceMerger(minSdk);
- 創建 QueueableResourceCompiler,因為 gradle3.x 以后支持了 aapt2,所以這里有兩種選擇 aapt 和 aapt2。其中 aapt2 有三種模式,OutOfProcessAaptV2,AaptV2Jni,QueueableAapt2,這里默認創建了 QueueableAapt2,resourceCompiler = QueueableAapt2
// MergeResources.doFullTaskAction() // makeAapt 中會判斷使用 aapt 還是 aapt2,這里以 aapt2 為例,返回的是 QueueableAapt2 對象 QueueableResourceCompiler resourceCompiler = makeAapt( aaptGeneration, getBuilder(), fileCache, crunchPng, variantScope, getAaptTempDir(), mergingLog)
- 將第一步獲取的 resourceSet 加入 ResourceMerger 中
for (ResourceSet resourceSet : resourceSets) { resourceSet.loadFromFiles(getILogger()); merger.addDataSet(resourceSet); }
- 創建 MergedResourceWriter
- 調用 ResourceMerger.mergeData 合並資源
// MergeResources.doFullTaskAction() merger.mergeData(writer, false /*doCleanUp*/);
- 調用 MergedResourceWriter 的 start(),addItem(),end() 方法,偽代碼如下:
// DataMerger.mergeData consumer.start() for item in sourceSets: // item 包括了需要處理的資源,包括 xml 和 圖片資源,每一個 item 對應的文件,會創建一個 CompileResourceRequest 對象,加入到 mCompileResourceRequests 里 consumer.addItem(item) consumer.end()
- 調用 QueueableAapt2 -> Aapt2QueuedResourceProcessor -> AaptProcess 處理資源
// MergedResourceWriter.end() Future<File> result = this.mResourceCompiler.compile(new CompileResourceRequest(fileToCompile, request.getOutput(), request.getFolderName(), this.pseudoLocalesEnabled, this.crunchPng)); // AaptProcess.compile public void compile( @NonNull CompileResourceRequest request, @NonNull Job<AaptProcess> job, @Nullable ProcessOutputHandler processOutputHandler) throws IOException { // ... // 使用 AaptV2CommandBuilder 生成 aapt2 命令 mWriter.write(joiner.join(AaptV2CommandBuilder.makeCompile(request))); mWriter.flush(); // 輸出命令 }
這一步調用 aapt2 命令去處理資源,處理完以后 xxx.xml.flat 格式
- doIncrementalTaskAction
增量任務過程和全量其實差異不大,只不過是在獲取 resourceSets 的時候,使用的是修改后的文件
4.3 processDebugResources
4.3.1 實現類
ProcessAndroidResources
4.3.2 整體實現圖
4.3.3 調用鏈路
ProcessAndroidResources.doFullTaskAction -> ProcessAndroidResources.invokeAaptForSplit -> AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink
4.3.4 主要代碼分析
ProcessAndroidResources 也是繼承自 IncrementalTask,但是沒有重寫 isIncremental,所以不是增量的 Task,直接看 doFullTaskAction 即可
- doFullTaskAction
這個里面代碼雖然多,但是主要的邏輯比較簡單,就是調用 aapt2 link 去生成資源包。
這里會處理 splits apk 相關的內容,關於 splits apk 具體可以查看 splits apk,簡單來說,就是可以按照屏幕分辨率,abis 來生成不同的 apk,從而讓特定用戶的安裝包變小。
分下面幾個步驟:
- 獲取 split 數據
List<ApkData> splitsToGenerate =
getApksToGenerate(outputScope, supportedAbis, buildTargetAbi, buildTargetDensity);
返回的是一個 ApkData 列表,ApkData 有三個子類,分別是 Main,Universal,FullSplit
我們配置 如下:
android { splits { // Configures multiple APKs based on screen density. density { // Configures multiple APKs based on screen density. enable true // Specifies a list of screen densities Gradle should not create multiple APKs for. exclude "ldpi", "xxhdpi", "xxxhdpi" // Specifies a list of compatible screen size settings for the manifest. compatibleScreens 'small', 'normal', 'large', 'xlarge' } } }
這里的 ApkData 會返回一個 Universal 和多個 FullSplit,Universal 代表的是主 apk,FullSplit 就是根據屏幕密度拆分的 apk。
如果我們沒有配置 splits apk,那么這里只會返回一個 Main 的實例,標識完整的 apk。
2. 先處理 main 和 不依賴 density 的 ApkData 資源
// ProcessAndroidResources.doFullTaskAction List<ApkData> apkDataList = new ArrayList<>(splitsToGenerate); for (ApkData apkData : splitsToGenerate) { if (apkData.requiresAapt()) { // 這里只處理 main 和不依賴 density 的資源 boolean codeGen = (apkData.getType() == OutputFile.OutputType.MAIN || apkData.getFilter(OutputFile.FilterType.DENSITY) == null); if (codeGen) { apkDataList.remove(apkData); invokeAaptForSplit( manifestsOutputs, libraryInfoList, packageIdFileSet, splitList, featureResourcePackages, apkData, codeGen, aapt); break; } } }
- 調用 invokeAaptForSplit 處理資源
// ProcessAndroidResources.invokeAaptForSplit void invokeAaptForSplit(...) { // ... String packageForR = null; File srcOut = null; File symbolOutputDir = null; File proguardOutputFile = null; File mainDexListProguardOutputFile = null; // 如果傳了 generateCode 參數,會生成 R.java if (generateCode) { packageForR = originalApplicationId; // we have to clean the source folder output in case the package name changed. srcOut = getSourceOutputDir(); if (srcOut != null) { FileUtils.cleanOutputDir(srcOut); } symbolOutputDir = textSymbolOutputDir.get(); proguardOutputFile = getProguardOutputFile(); mainDexListProguardOutputFile = getMainDexListProguardOutputFile(); } // ... getBuilder().processResources(aapt, config); }
- 調用 AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink 處理資源,生成資源包以及 R.java 文件
- 處理其他 ApkData 資源,這里只會生成資源包而不會生成 R.java 文件
關於 aapt2 的 compile 和 link 參數,可以在 developer.android.com/studio/comm… 這里看
4.4 processDebugManifest
4.4.1 實現類
MergeManifests
4.4.2 整體實現圖

4.4.3 調用鏈路
MergeManifests.dofFullTaskAction -> AndroidBuilder.mergeManifestsForApplication -> Invoker.merge -> ManifestMerge2.merge
4.4.4 主要代碼分析
MergeManifests 也是繼承了 IncrementalTask,但是沒有實現 isIncremental,所以只看其 doFullTaskAction 即可。
這個 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 等等
performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get()); // ManifestMerger2.performSystemPropertiesInjection protected void performSystemPropertiesInjection( @NonNull MergingReport.Builder mergingReport, @NonNull XmlDocument xmlDocument) { for (ManifestSystemProperty manifestSystemProperty : ManifestSystemProperty.values()) { String propertyOverride = mSystemPropertyResolver.getValue(manifestSystemProperty); if (propertyOverride != null) { manifestSystemProperty.addTo( mergingReport.getActionRecorder(), xmlDocument, propertyOverride); } } }
4.合並 flavor,buildType 中的 manifest
for (File inputFile : mFlavorsAndBuildTypeFiles) { LoadedManifestInfo overlayDocument = load( new ManifestInfo(null, inputFile, XmlDocument.Type.OVERLAY, Optional.of(mainPackageAttribute.get().getValue())), selectors, mergingReportBuilder); // 檢查 package 定義 Optional<XmlAttribute> packageAttribute = overlayDocument.getXmlDocument().getPackage(); if (loadedMainManifestInfo.getOriginalPackageName().isPresent() && packageAttribute.isPresent() && !loadedMainManifestInfo.getOriginalPackageName().get().equals( packageAttribute.get().getValue())) { // 如果 package 定義重復的話,會輸出下面信息,我們平時應該或多或少見過類似的錯誤 String message = mMergeType == MergeType.APPLICATION ? String.format( "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n" + "\thas a different value=(%3$s) " + "declared in main manifest at %4$s\n" + "\tSuggestion: remove the overlay declaration at %5$s " + "\tand place it in the build.gradle:\n" + "\t\tflavorName {\n" + "\t\t\tapplicationId = \"%2$s\"\n" + "\t\t}", packageAttribute.get().printPosition(), packageAttribute.get().getValue(), mainPackageAttribute.get().getValue(), mainPackageAttribute.get().printPosition(), packageAttribute.get().getSourceFile().print(true)) : String.format( "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n" + "\thas a different value=(%3$s) " + "declared in main manifest at %4$s", packageAttribute.get().printPosition(), packageAttribute.get().getValue(), mainPackageAttribute.get().getValue(), mainPackageAttribute.get().printPosition()); // ... return mergingReportBuilder.build(); } }
- 合並依賴庫的 manifest
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) { mLogger.verbose("Merging library manifest " + libraryDocument.getLocation()); xmlDocumentOptional = merge( xmlDocumentOptional, libraryDocument, mergingReportBuilder); if (!xmlDocumentOptional.isPresent()) { return mergingReportBuilder.build(); } }
- 處理 manifest 的 placeholders
performPlaceHolderSubstitution(loadedMainManifestInfo, xmlDocumentOptional.get(), mergingReportBuilder, severity);
- 之后對最終合並后的 manifest 中的一些屬性重新進行一次替換,類似步驟 4
- 保存 manifest 到 build/intermediates/manifest/fullxxx/AndroidManifest.xml 這就生成了最終的 Manifest 文件
4.5 transformClassesWithDexBuilderForDebug
4.5.1 實現類
DexArchiveBuilderTransform
4.5.2 整體實現圖

4.5.3 調用鏈路
DexArchiveBuilderTransform.transform -> DexArchiveBuilderTransform.convertJarToDexArchive -> DexArchiveBuilderTransform.convertToDexArchive -> DexArchiveBuilderTransform.launchProcessing -> DxDexArchiveBuilder.convert
4.5.4 主要代碼分析
在 DexArchiveBuilderTransform 中,對 class 的處理分為兩種方式,一種是對 目錄下的 class 進行處理,一種是對 .jar 里的 class 進行處理。
為什么要分為這兩種方式呢?.jar 中的 class 一般來說都是依賴庫,基本上不會改變,gradle 在這里做了一個緩存,但是兩種方式最終都會調用到 convertToDexArchive,可以說是殊途同歸吧。
- convertJarToDexArchive 處理 jar
處理 .jar 時,會對 jar 包中的每一個 class 都單獨打成一個 .dex 文件,之后還是放在 .jar 包中
private List<File> convertJarToDexArchive( @NonNull Context context, @NonNull JarInput toConvert, @NonNull TransformOutputProvider transformOutputProvider) throws Exception { File cachedVersion = cacheHandler.getCachedVersionIfPresent(toConvert); if (cachedVersion == null) { // 如果沒有緩存,調用 convertToDexArchive 去生成 dex return convertToDexArchive(context, toConvert, transformOutputProvider, false); } else { // 如果有緩存,直接使用緩存的 jar File outputFile = getPreDexJar(transformOutputProvider, toConvert, null); Files.copy( cachedVersion.toPath(), outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING); // no need to try to cache an already cached version. return ImmutableList.of(); } }
- convertToDexArchive 處理 dir 以及 jar 的后續處理
對 dir 處理使用 convertToDexArchive
其中會調用 launchProcessing
private static void launchProcessing( @NonNull DexConversionParameters dexConversionParameters, @NonNull OutputStream outStream, @NonNull OutputStream errStream) throws IOException, URISyntaxException { // ... boolean hasIncrementalInfo = dexConversionParameters.isDirectoryBased() && dexConversionParameters.isIncremental; // 判斷 class 是否新增或者修改過,如果新增或者修改過,就需要處理 Predicate<String> toProcess = hasIncrementalInfo ? path -> { Map<File, Status> changedFiles = ((DirectoryInput) dexConversionParameters.input) .getChangedFiles(); File resolved = inputPath.resolve(path).toFile(); Status status = changedFiles.get(resolved); return status == Status.ADDED || status == Status.CHANGED; } : path -> true; bucketFilter = bucketFilter.and(toProcess); try (ClassFileInput input = ClassFileInputs.fromPath(inputPath)) { // 內部調用 dx 或者 d8 去打 dex dexArchiveBuilder.convert( input.entries(bucketFilter), Paths.get(new URI(dexConversionParameters.output)), dexConversionParameters.isDirectoryBased()); } catch (DexArchiveBuilderException ex) { throw new DexArchiveBuilderException("Failed to process " + inputPath.toString(), ex); } }
在 launchProcessing 中,有下面幾個步驟:
- 判斷目錄下的 class 是否新增或者修改過
- 調用 DexArchiveBuilder.build 去處理修改過的 class
- DexArchiveBuilder 有兩個子類,D8DexArchiveBuilder 和 DxDexArchiveBuilder,分別是調用 d8 和 dx 去打 dex
4.6 transformDexArchiveWithExternalLibsDexMergerForDebug
4.6.1 實現類
ExternalLibsMergerTransform
4.6.2 整體實現圖

4.6.3 調用鏈路
這一步是處理依賴庫的 dex,把上一步生成的依賴庫的 dex merge 成一個 dex
// dx ExternalLibsMergerTransform.transform -> DexMergerTransformCallable.call -> DxDexArchiveMerger.mergeDexArchives -> DxDexArchiveMerger.mergeMonoDex -> DexArchiveMergerCallable.call -> DexMerger.merge
// d8 ExternalLibsMergerTransform.transform -> DexMergerTransformCallable.call -> D8DexArchiveMerger.mergeDexArchives -> 調用 D8 命令
這里邏輯比較簡單,就不具體分析了
4.7 transformDexArchiveWithDexMergerForDebug
4.7.1 實現類
DexMergerTransform
4.7.2 整體實現圖

4.7.3 調用鏈路
和上一步類似
// dx DexMergerTransform.transform -> DexMergerTransform.handleLegacyAndMonoDex -> DexMergerTransformCallable.call -> DxDexArchiveMerger.mergeDexArchives -> DxDexArchiveMerger.mergeMonoDex -> DexArchiveMergerCallable.call -> DexMerger.merge
// d8 DexMergerTransform.transform -> DexMergerTransform.handleLegacyAndMonoDex -> DexMergerTransformCallable.call -> D8DexArchiveMerger.mergeDexArchives -> 調用 D8 命令
五、本文重點
- Android Gradle Plugin 中各個 Task 的作用及實現類,具體可參考文中第二節「Task 對應實現類」
- 如何閱讀 Task 的代碼