Android gradle打包涉及task源碼解析(三)

文章序號

此篇文章將分析如下5個task。

:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources UP-TO-DATE
:app:mergeDebugResources UP-TO-DATE
:app:createDebugCompatibleScreenManifests UP-TO-DATE
:app:processDebugManifest

generateDebugResValues

  • 準備

在項目的app/gradle中添加如下代碼

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            // 添加代碼
            resValue "string", "AppName", "app_release"
        }
        // 添加代碼
        debug {
            resValue "string", "AppName", "app_debug"
        }
    }

接著執(zhí)行命令./gradlew generateDebugResValues

  • inputs&outputs
output file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/build/generated/res/resValues/debug

輸出文件目錄下面會生成generated.xml文件,文件內容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Automatically generated file. DO NOT MODIFY -->

    <!-- Values from build type: debug -->
    <string name="AppName" translatable="false">app_debug</string>

</resources>

生成的內容我們可以直接在xml中使用(android:text="@string/AppName"),也可以在代碼中使用(getResources().getString(R.string.AppName);)。

  • 源碼

https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/GenerateResValues.java

  • 主要代碼邏輯

GenerateResValues.java中的generate()方法

    @TaskAction
    void generate() throws IOException, ParserConfigurationException {
        // 1、輸出路徑,就是我們output中的目錄
        File folder = getResOutputDir();
        // 2、這里的getItems()就是獲得我們在代碼中設置的resValues
        List<Object> resolvedItems = getItems();

        if (resolvedItems.isEmpty()) {
            FileUtils.cleanOutputDir(folder);
        } else {
            // 3、在resolvedItems不等于空的時候,通過ResValueGenerator生成我們的generated.xml文件
            ResValueGenerator generator = new ResValueGenerator(folder);
            generator.addItems(getItems());
            generator.generate();
        }
    }

通過上面的分析,我們知道generateDebugResValues 任務就是把我們在gradle里面配置的resValue讀取到,然后在/build/generated/res/resValues/debug目錄下生成generate.xml文件,該文件里面的內容,可直接在xml文件和代碼中使用,方便一些動態(tài)化的配置。

generateDebugResources

很遺憾該任務的相關邏輯沒有找到,有知道的麻煩留言告知一下。

mergeDebugResources

執(zhí)行命令./gradlew mergeDebugResources

  • inputs&outputs
input file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/build/generated/res/resValues/debug
input file:/Users/zhengchao/.gradle/caches/transforms-1/files-1.1/appcompat-v7-26.1.0.aar/a7cc521b4567369eba0ddb355f44a660/res
input file:/Users/zhengchao/.gradle/caches/transforms-1/files-1.1/constraint-layout-1.1.3.aar/604f3f8b356720eda7bfab425c06a06e/res
input file:/Users/zhengchao/.gradle/caches/transforms-1/files-1.1/support-media-compat-26.1.0.aar/500188dffd88c5be8587eb6372bbf06d/res
input file:/Users/zhengchao/.gradle/caches/transforms-1/files-1.1/support-compat-26.1.0.aar/9806df9e60c4aacc7f9f357a91ad2e92/res
input file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/build/generated/res/rs/debug
input file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/src/main/res
input file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/src/debug/res
---------------------------------------------------
output file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/build/intermediates/blame/res/debug
output file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/build/generated/res/pngs/debug
output file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/build/intermediates/incremental/mergeDebugResources
output file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/build/intermediates/res/merged/debug

根據輸入文件路徑,大致有這幾種:

1、generateDebugResValues任務生成的resValues文件(generated/res/resValues/debug);

2、引用的aar包里面的資源(appcompat-v7-26.1.0.aar/a7cc521b4567369eba0ddb355f44a660/res,constraint-layout-1.1.3.aar/604f3f8b356720eda7bfab425c06a06e/res);

3、compileDebugRenderscript 任務生的Renderscript文件(generated/res/rs/debug);

4、項目中的res資源文件(TasksPro/app/src/main/res、TasksPro/app/src/debug/res);

輸入文件經過mergeDebugResources任務處理后,生產的文件有如下幾種:

1、png圖片集合(generated/res/pngs/debug);

2、merge后的資源集合(incremental/mergeDebugResources);

3、資源映射關系集合(intermediates/res/merged/debug);

4、merge操作的日志記錄(intermediates/blame/res/debug);

  • 源碼

https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/MergeResources.java

  • 主要代碼邏輯

MergeResources.java->doFullTaskAction()方法

    @Override
    protected void doFullTaskAction() throws IOException, ExecutionException, JAXBException {
        // 1、得到ResourcePreprocessor子類對象
        ResourcePreprocessor preprocessor = getPreprocessor();

        // 2、得到task的output目錄
        // this is full run, clean the previous output
        File destinationDir = getOutputDir();
        FileUtils.cleanOutputDir(destinationDir);

        // 3、得到inputs文件目錄集合
        List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);

        // 4、生成ResourceMerger 對象
        // create a new merger and populate it with the sets.
        ResourceMerger merger = new ResourceMerger(minSdk);
        // 5、mergeingLog 記錄
        MergingLog mergingLog =
                getBlameLogFolder() != null ? new MergingLog(getBlameLogFolder()) : null;

        // 6、resourceCompiler,實際是Appt工具對象
        try (QueueableResourceCompiler resourceCompiler =
                processResources
                        ? makeAapt(
                                aaptGeneration,
                                getBuilder(),
                                fileCache,
                                crunchPng,
                                variantScope,
                                getAaptTempDir(),
                                mergingLog)
                        : QueueableResourceCompiler.NONE) {

            for (ResourceSet resourceSet : resourceSets) {
                resourceSet.loadFromFiles(getILogger());
                merger.addDataSet(resourceSet);
            }

            MergedResourceWriter writer =
                    new MergedResourceWriter(
                            workerExecutorFacade,
                            destinationDir,
                            getPublicFile(),
                            mergingLog,
                            preprocessor,
                            resourceCompiler,
                            getIncrementalFolder(),
                            dataBindingLayoutProcessor,
                            mergedNotCompiledResourcesOutputDirectory,
                            pseudoLocalesEnabled,
                            getCrunchPng());
            // 7、執(zhí)行merge resource 操作
            merger.mergeData(writer, false /*doCleanUp*/);

            if (dataBindingLayoutProcessor != null) {
                dataBindingLayoutProcessor.end();
            }

            // No exception? Write the known state.
            merger.writeBlobTo(getIncrementalFolder(), writer, false);
        } catch (MergingException e) {
            System.out.println(e.getMessage());
            merger.cleanBlob(getIncrementalFolder());
            throw new ResourceException(e.getMessage(), e);
        } finally {
            cleanup();
        }
    }

第一步:得到ResourcePreprocessor子類對象,實際是MergeResourcesVectorDrawableRenderer對象,該類又繼承了VectorDrawableRenderer,該類如下:

/**
 * Generates PNG images (and XML copies) from VectorDrawable files.
*/
public class VectorDrawableRenderer implements ResourcePreprocessor {

通過說明,可以知道該類是通過VectorDrawable文件生產PNG圖片,或者拷貝 xml文件。

第二步:得到task的output目錄,實際上就是/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/build/intermediates/res/merged/debug目錄;

第三步:得到inputs文件目錄集合,調用getConfiguredResourceSets(preprocessor)方法,該方法就是得到inputs路徑;

第四步:生成ResourceMerger 對象。該類實現(xiàn)了DataMerger抽象類,主要的merge邏輯均在此類里面實現(xiàn),此處不再展開,有興趣的自行閱讀:https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java

第五步:mergeingLog 記錄,就是intermediates/blame/res/debug目錄,記錄操作日志。

第六步:resourceCompiler,實際是Appt工具對象。

第七步:執(zhí)行merge resource 操作

mergeDebugResources任務是把依賴的庫和工程中的資源進行merge操作。

createDebugCompatibleScreenManifests

  • 準備(gradle splits配置)

在項目的app/gradle android模塊下添加如下代碼:

splits {
        // Screen density split settings
        density {

            // Enable or disable the density split mechanism
            enable true

            // Exclude these densities from splits
            exclude "ldpi", "tvdpi", "xxhdpi", "xxxhdpi"
        }
    }

執(zhí)行命令:./gradlew createDebugCompatibleScreenManifests

  • inputs&outputs
output file:/Users/zhengchao/Documents/AndroidSpace/OpenSpace/TasksPro/app/build/intermediates/manifests/density/debug

在output目錄下會生成hdpi/AndroidManigest.xml,mdpi/AndroidManigest.xml,xhdpi/AndroidManigest.xml和output.json文件。Manifest文件內容如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="">

    <uses-sdk android:minSdkVersion="14"/>
    <compatible-screens>
    </compatible-screens>
</manifest>

output.json文件內容如下(經格式化處理的):

[{
    "outputType": {
        "type": "COMPATIBLE_SCREEN_MANIFEST"
    },
    "apkInfo": {
        "type": "FULL_SPLIT",
        "splits": [{
            "filterType": "DENSITY",
            "value": "xhdpi"
        }],
        "versionCode": 1
    },
    "path": "xhdpi/AndroidManifest.xml",
    "properties": {}
}, {
    "outputType": {
        "type": "COMPATIBLE_SCREEN_MANIFEST"
    },
    "apkInfo": {
        "type": "FULL_SPLIT",
        "splits": [{
            "filterType": "DENSITY",
            "value": "hdpi"
        }],
        "versionCode": 1
    },
    "path": "hdpi/AndroidManifest.xml",
    "properties": {}
}, {
    "outputType": {
        "type": "COMPATIBLE_SCREEN_MANIFEST"
    },
    "apkInfo": {
        "type": "FULL_SPLIT",
        "splits": [{
            "filterType": "DENSITY",
            "value": "mdpi"
        }],
        "versionCode": 1
    },
    "path": "mdpi/AndroidManifest.xml",
    "properties": {}
}]
  • 源碼

https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/CompatibleScreensManifest.java

  • 主要代碼邏輯
    public void generateAll() throws IOException {
        // 1、遍歷所有的density,通過generate方法生產響應的manifest文件
        // process all outputs.
        outputScope.parallelForEach(
                VariantScope.TaskOutputType.COMPATIBLE_SCREEN_MANIFEST, this::generate);
        // 2、生成json文件
        // now write the metadata file.
        outputScope.save(
                ImmutableList.of(VariantScope.TaskOutputType.COMPATIBLE_SCREEN_MANIFEST),
                outputFolder);
    }
    @Nullable
    public File generate(ApkData apkData) throws IOException {
        String density = apkData.getFilter(com.android.build.OutputFile.FilterType.DENSITY);
        if (density == null) {
            return null;
        }

        StringBuilder content = new StringBuilder();
        content.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
                .append("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n")
                .append("    package=\"\">\n")
                .append("\n");
        if (minSdkVersion.get() != null) {
            content.append("    <uses-sdk android:minSdkVersion=\"")
                    .append(minSdkVersion.get())
                    .append("\"/>\n");
        }
        content.append("    <compatible-screens>\n");

        // convert unsupported values to numbers.
        density = convert(density, Density.XXHIGH, Density.XXXHIGH);

        for (String size : getScreenSizes()) {
            content.append(
                    "        <screen android:screenSize=\"").append(size).append("\" "
                    + "android:screenDensity=\"").append(density).append("\" />\n");
        }

        content.append(
                "    </compatible-screens>\n" +
                "</manifest>");


        File splitFolder = new File(outputFolder, apkData.getDirName());
        FileUtils.mkdirs(splitFolder);
        File manifestFile = new File(splitFolder, SdkConstants.ANDROID_MANIFEST_XML);

        Files.write(content.toString(), manifestFile, Charsets.UTF_8);
        return manifestFile;
    }

代碼很簡單第一步遍歷density,然后生產相應的manifest文件。第二步生成output.json文件。

processDebugManifest

執(zhí)行命令:./gradlew processDebugManifest

  • inputs&outputs
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/manifests/density/debug
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/src/main/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/appcompat-v7-26.1.0.aar/6b443e96f1af9aa241aaa70576c67a57/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/constraint-layout-1.1.3.aar/f44da5c361a1f52801511229596f72e7/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/animated-vector-drawable-26.1.0.aar/9c804d63d6f065a8f9945f9ad94fee0e/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/support-vector-drawable-26.1.0.aar/4e56cc34abf77378e2b8d16ee237c82d/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/support-v4-26.1.0.aar/3bf8586900bd31e222ef8b68bfd6e744/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/support-media-compat-26.1.0.aar/267524a16ca7128dd9cef3c19f394439/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/support-fragment-26.1.0.aar/77cf518e9868987a283f04cec221fefa/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/support-core-utils-26.1.0.aar/8634ab1afa6a5a1a947a7bd163aba14f/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/support-core-ui-26.1.0.aar/8902e2a864b44d47c26fbc80fdafe175/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/support-compat-26.1.0.aar/3e4c87483eacfb4c962d7380a59a114d/AndroidManifest.xml
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/runtime-1.0.0.aar/ed085e7b9476f7a9fef4ffbb323166ba/AndroidManifest.xml
---------------------------------------------------
output file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/manifests/instant-run/debug
output file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/manifests/full/debug
output file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/outputs/logs/manifest-merger-debug-report.txt

根據input file路徑,可知輸入分為三種類型:

1、createDebugCompatibleScreenManifests 任務生成的density規(guī)則(intermediates/manifests/density/debug);

2、項目的manifest文件(TasksPro/app/src/main/AndroidManifest.xml);

3、依賴的aar包的manifest和運行環(huán)境的manifest文件(appcompat-v7-26.1.0.aar/.../AndroidManifest.xml,runtime-1.0.0.aar/.../AndroidManifest.xml)。

根據output file路徑,可知輸出文件也為三種:

1、instant-run模式下的輸出(manifests/instant-run/debug);

2、正常模式下的輸出(manifests/full/debug);

3、merge manifest 的log記錄(outputs/logs/manifest-merger-debug-report.txt);

  • 源碼

https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/MergeManifests.java

  • 主要代碼邏輯

MergeManifest.java中的doFullTaskAction()方法。

    protected void doFullTaskAction() throws IOException {
        // 1、生成compatibleScreenManifests集合
        // read the output of the compatible screen manifest.
        Collection<BuildOutput> compatibleScreenManifests =
                BuildOutputs.load(
                        VariantScope.TaskOutputType.COMPATIBLE_SCREEN_MANIFEST,
                        compatibleScreensManifest);
        // 2、重新設置packageName
        String packageOverride;
        if (packageManifest != null && !packageManifest.isEmpty()) {
            packageOverride =
                    ApplicationId.load(packageManifest.getSingleFile()).getApplicationId();
        } else {
            packageOverride = getPackageOverride();
        }

        @Nullable BuildOutput compatibleScreenManifestForSplit;

        // 3、根據splits生成ApkData集合
        List<ApkData> splitsToGenerate =
                ProcessAndroidResources.getApksToGenerate(
                        outputScope, supportedAbis, buildTargetAbi, buildTargetDensity);
        
        // 4、遍歷splitsToGenerate集合
        // FIX ME : multi threading.
        for (ApkData apkData : splitsToGenerate) {
            // 5、指定分辨率的BuildOutput對象
            compatibleScreenManifestForSplit =
                    OutputScope.getOutput(
                            compatibleScreenManifests,
                            VariantScope.TaskOutputType.COMPATIBLE_SCREEN_MANIFEST,
                            apkData);
            // 6、正常的輸出
            File manifestOutputFile =
                    FileUtils.join(
                            getManifestOutputDirectory(),
                            apkData.getDirName(),
                            SdkConstants.ANDROID_MANIFEST_XML);
            // 7、instantRun 模式輸出
            File instantRunManifestOutputFile =
                    FileUtils.join(
                            getInstantRunManifestOutputDirectory(),
                            apkData.getDirName(),
                            SdkConstants.ANDROID_MANIFEST_XML);
            // 8、生成mergingReport對象
            MergingReport mergingReport =
                    getBuilder()
                            .mergeManifestsForApplication(
                                    getMainManifest(),
                                    getManifestOverlays(),
                                    computeFullProviderList(compatibleScreenManifestForSplit),
                                    getFeatureName(),
                                    packageOverride,
                                    apkData.getVersionCode(),
                                    apkData.getVersionName(),
                                    getMinSdkVersion(),
                                    getTargetSdkVersion(),
                                    getMaxSdkVersion(),
                                    manifestOutputFile.getAbsolutePath(),
                                    // no aapt friendly merged manifest file necessary for applications.
                                    null /* aaptFriendlyManifestOutputFile */,
                                    instantRunManifestOutputFile.getAbsolutePath(),
                                    ManifestMerger2.MergeType.APPLICATION,
                                    variantConfiguration.getManifestPlaceholders(),
                                    getOptionalFeatures(),
                                    getReportFile());
            XmlDocument mergedXmlDocument =
                    mergingReport.getMergedXmlDocument(MergingReport.MergedManifestKind.MERGED);
            ImmutableMap<String, String> properties =
                    mergedXmlDocument != null
                            ? ImmutableMap.of(
                                    "packageId",
                                    mergedXmlDocument.getPackageName(),
                                    "split",
                                    mergedXmlDocument.getSplitName(),
                                    SdkConstants.ATTR_MIN_SDK_VERSION,
                                    mergedXmlDocument.getMinSdkVersion())
                            : ImmutableMap.of();

            outputScope.addOutputForSplit(
                    VariantScope.TaskOutputType.MERGED_MANIFESTS,
                    apkData,
                    manifestOutputFile,
                    properties);
            outputScope.addOutputForSplit(
                    VariantScope.TaskOutputType.INSTANT_RUN_MERGED_MANIFESTS,
                    apkData,
                    instantRunManifestOutputFile,
                    properties);
        }
        // 9、存儲full模式下的output.json文件
        outputScope.save(
                ImmutableList.of(VariantScope.TaskOutputType.MERGED_MANIFESTS),
                getManifestOutputDirectory());
        // 10、存儲instantRun模式下的output.json文件
        outputScope.save(
                ImmutableList.of(VariantScope.TaskOutputType.INSTANT_RUN_MERGED_MANIFESTS),
                getInstantRunManifestOutputDirectory());
    }

第一步:從`createDebugCompatibleScreenManifests'任務的輸出讀取,生成compatibleScreenManifests集合。所以這里的值有三個(hdpi、mdpi、xhdpi)BuildOutput對象;

第二步:重新設置packageName。如果我們同時在Manifest.xml和gradle里面同時設置packageId,且id值不同,則會在此用gradle里面的package覆蓋Manifest.xml里面的id值。

第三步:根據splits的配置,生成apkData集合splitsToGenerate對象;

第四步:遍歷splitsToGenerate集合,生成不同的輸出文件;

第五步:進入不同的split遍歷,得到指定分辨率的BuildOutput對象;

第六步:正常的manifest輸出文件。(/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/manifests/full/debug/universal/AndroidManifest.xml

第七步:instantRun模式下的Manifest輸出。

第八步:生成mergingReport對象,調用的是AndroidBuilder中的mergeManifestsForApplication()方法,該方法如下:

    /** Invoke the Manifest Merger version 2. */
    public MergingReport mergeManifestsForApplication(
            @NonNull File mainManifest,
            @NonNull List<File> manifestOverlays,
            @NonNull List<? extends ManifestProvider> dependencies,
            @Nullable String featureName,
            String packageOverride,
            int versionCode,
            String versionName,
            @Nullable String minSdkVersion,
            @Nullable String targetSdkVersion,
            @Nullable Integer maxSdkVersion,
            @NonNull String outManifestLocation,
            @Nullable String outAaptSafeManifestLocation,
            @Nullable String outInstantRunManifestLocation,
            ManifestMerger2.MergeType mergeType,
            Map<String, Object> placeHolders,
            @NonNull List<Invoker.Feature> optionalFeatures,
            @Nullable File reportFile) {
            ...
            // 執(zhí)行merge操作,調用的是ManifestMerger2類的merge()方法。
            MergingReport mergingReport = manifestMergerInvoker.merge();
            ...
    }

此任務的核心merge邏輯都在這一步,詳細的細節(jié)有興趣的同學自行看源碼:https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger2.java?autodive=0%2F%2F%2F%2F

第九步:存儲full模式下的output.json文件;

第十步:存儲instantRun模式下的output.json文件

經過上面分析可知,processDebugManifest任務實際上做的是manifest文件的merge操作,將三種輸入類型的manifest合成一個manifest文件,并生成相應操作日志文件。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容