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

上文回顧

上篇文章裏講了 android gradle plugin 的總體流程,引入插件之後生成了不少 Task,這篇文章就談談生成的這些 Task 都有什麼用處,以及一些主要 Task 的實現java

預備知識

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

看完本文能夠達到什麼程度

  1. 瞭解 android gradle plugin 中各個 task 做用
  2. 瞭解 android gradle plugin 中主要 task 的實現

閱讀前準備工做

1.項目添加 android gradle plugin 依賴android

compile 'com.android.tools.build:gradle:3.0.1'
複製代碼

經過這種方式,能夠直接依賴 plugin 的源碼,讀起來比較方便git

2.官方對照源碼地址 android gradle plugin 源碼地址github

你們能夠直接 clone EasyGradle 項目,把 app/build.gradle 裏的 implementation 'com.android.tools.build:gradle:3.0.1' 註釋打開就能夠了。json

Gradle的基本使用Android Gradle Plugin 主要流程分析 裏,咱們知道了 gradle 中 task 的重要性,以及 android gradle plugin 的主要流程,這一篇就來分析一下 android gradle plugin 中一些重要的 task 是怎麼執行的。緩存

1、Android 打包流程

在介紹 Android Gradle Plugin Task 以前,咱們先看看一個 apk 的構建流程,先放一張官方流程圖: bash

android-app

官方介紹的流程以下:app

  1. 編譯器將您的源代碼轉換成 DEX(Dalvik Executable) 文件(其中包括 Android 設備上運行的字節碼),將全部其餘內容轉換成已編譯資源。
  2. APK 打包器將 DEX 文件和已編譯資源合併成單個 APK。 不過,必須先簽署 APK,才能將應用安裝並部署到 Android 設備上。
  3. APK 打包器使用調試或發佈密鑰庫簽署您的 APK:
  4. 在生成最終 APK 以前,打包器會使用 zipalign 工具對應用進行優化,減小其在設備上運行時佔用的內存。

那麼以 Task 的維度來看 apk 的打包,是什麼流程呢?咱們先執行下面的命令,看一下打包一個 apk 須要哪些 taskide

首先咱們看一下 打包一個 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

2、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,錨點

3、如何去讀 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 方法的實現

4、重點 Task 實現分析

上面每一個 task 已經簡單說明了具體作什麼以及對應的實現類,下面選了幾個比較重要的來分析一下其實現
爲何分析這幾個呢?這幾個表明了 gradle 自動生成代碼,資源的處理,以及 dex 的處理,算是 apk 打包過程當中比較重要的幾環。
generateDebugBuildConfig
processDebugManifest
mergeDebugResources
processDebugResources
transformClassesWithDexBuilderForDebug
transformDexArchiveWithExternalLibsDexMergerForDebug
transformDexArchiveWithDexMergerForDebug

分析過程主要下面幾個步驟,實現類,總體實現圖,調用鏈路(方便之後回看代碼),以及重要代碼分析

4.1 generateDebugBuildConfig

4.1.1 實現類

GenerateBuildConfig

4.1.2 總體實現圖

generateDebugBuildConfig

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 總體實現圖

mergeDebugResources

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 總體實現圖

processDebugResources

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 總體實現圖

processDebugManifest

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);
        }
    }
}
複製代碼
  1. 合併 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 總體實現圖

transformClassesWithDexBuilderForDebug

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 總體實現圖

transformDexArchiveWithExternalLibsDexMergerForDebug

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 總體實現圖

transformDexArchiveWithDexMergerForDebug

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 命令
複製代碼

5、本文重點

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

關於我

about
相關文章
相關標籤/搜索