Gradle之Android Gradle Plugin 主要 Task 分析(三)


【Android 修煉手冊】Gradle 篇 -- Android Gradle Plugin 主要 Task 分析

預備知識

  1. 理解 gradle 的基本開發
  2. 了解 gradle task 和 plugin 使用及開發
  3. 了解 android gradle plugin 的使用

看完本文可以達到什么程度

  1. 了解 android gradle plugin 中各個 task 作用
  2. 了解 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 的構建流程,先放一張官方流程圖:

官方介紹的流程如下:

  1. 編譯器將您的源代碼轉換成 DEX(Dalvik Executable) 文件(其中包括 Android 設備上運行的字節碼),將所有其他內容轉換成已編譯資源。
  2. APK 打包器將 DEX 文件和已編譯資源合並成單個 APK。 不過,必須先簽署 APK,才能將應用安裝並部署到 Android 設備上。
  3. APK 打包器使用調試或發布密鑰庫簽署您的 APK:
  4. 在生成最終 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 的代碼

  1. 看 Task 繼承的父類,一般來說,會繼承 DefaultTask,IncrementalTask
  2. 看 @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 的代碼,主要有四步:

  1. 首先這個 Task 要繼承 IncrementalTask,
  2. 其次看 isIncremental 方法,如果返回 true,說明支持增量,返回 false 則不支持
  3. 然后看 doFullTaskAction 方法,是全量的時候執行的操作
  4. 最后看 doIncrementalTaskAction 方法,這里是增量的時候執行的操作

如何讀 Transform

  1. 繼承自 Transform
  2. 看其 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 中,主要生成代碼的步驟如下:

  1. 生成 BuildConfigGenerator
  2. 添加默認的屬性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME
  3. 添加自定義屬性
  4. 調用 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
  1. 通過 getConfiguredResourceSets() 獲取 resourceSets,包括了自己的 res/ 和 依賴庫的 res/ 以及 build/generated/res/rs
// MergeResources.doFullTaskAction()
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
  1. 創建 ResourceMerger
// MergeResources.doFullTaskAction()
ResourceMerger merger = new ResourceMerger(minSdk);
  1. 創建 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)
  1. 將第一步獲取的 resourceSet 加入 ResourceMerger 中
for (ResourceSet resourceSet : resourceSets) {
    resourceSet.loadFromFiles(getILogger());
    merger.addDataSet(resourceSet);
}
  1. 創建 MergedResourceWriter
  2. 調用 ResourceMerger.mergeData 合並資源
// MergeResources.doFullTaskAction()
merger.mergeData(writer, false /*doCleanUp*/);
  1. 調用 MergedResourceWriter 的 start(),addItem(),end() 方法,偽代碼如下:
// DataMerger.mergeData
consumer.start()
for item in sourceSets:
  // item 包括了需要處理的資源,包括 xml 和 圖片資源,每一個 item 對應的文件,會創建一個 CompileResourceRequest 對象,加入到 mCompileResourceRequests 里
  consumer.addItem(item)
consumer.end()
  1. 調用 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,從而讓特定用戶的安裝包變小。
    分下面幾個步驟:
  1. 獲取 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;
        }
    }
}
  1. 調用 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);
}
  1. 調用 AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink 處理資源,生成資源包以及 R.java 文件
  2. 處理其他 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 過程 。 主要有幾個步驟:

  1. 獲取依賴庫的 manifest 信息,用 LoadedManifestInfo 標識
  2. 獲取主 module 的 manifest 信息
  3. 替換主 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();
    }
}
  1. 合並依賴庫的 manifest
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
    mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
    xmlDocumentOptional = merge(
            xmlDocumentOptional, libraryDocument, mergingReportBuilder);
    if (!xmlDocumentOptional.isPresent()) {
        return mergingReportBuilder.build();
    }
}
  1. 處理 manifest 的 placeholders
performPlaceHolderSubstitution(loadedMainManifestInfo, xmlDocumentOptional.get(), mergingReportBuilder, severity);
  1. 之后對最終合並后的 manifest 中的一些屬性重新進行一次替換,類似步驟 4
  2. 保存 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 中,有下面幾個步驟:

  1. 判斷目錄下的 class 是否新增或者修改過
  2. 調用 DexArchiveBuilder.build 去處理修改過的 class
  3. 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 命令

五、本文重點

  1. Android Gradle Plugin 中各個 Task 的作用及實現類,具體可參考文中第二節「Task 對應實現類」
  2. 如何閱讀 Task 的代碼


 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM