【靈魂七問】深度探索 Gradle 自動化構建技術(5、Gradle 插件架構實現原理剖析 — 下)

前言

成爲一名優秀的Android開發,須要一份完備的知識體系,在這裏,讓咱們一塊兒成長爲本身所想的那樣~。

目前,Gradle 自動化技術愈來愈重要,也有許多同窗已經可以製做出本身的 Gradle 插件,可是一直有一些 「梗」 遺留在咱們腦海中,無時無刻不提醒着咱們,你真的掌握了嗎?例如,「梗1」:Gradle 插件的總體實現架構?我:...,「梗2」:Android Gradle 插件更新歷史有哪些重要優化或者改進?我:..., 「梗3」:Gradle 構建的核心流程是怎樣的?我:...,「梗4」:Gradle 中依賴實現的原理?我:..., 「梗5」:AppPlugin 構建流程?我:..., 「梗6」:assembleDebug 打包流程?我:...., 「梗7」:一些很重要 Task 實現原理可否說說?我:... 。是否有不少點並無真正地去了解過呢?html

思惟導圖

目錄

  • 1、Gradle 插件實現架構概述
  • 2、瞭解 Android Gradle Plugin 的更新歷史
    • 一、Android Gradle Plugin V3.5.0(2019 年 8 月)
    • 二、Android Gradle Plugin V3.4.0(2019 年 4 月)
    • 三、Android Gradle Plugin V3.3.0(2019 年 1 月)
    • 四、Android Gradle Plugin V3.2.0(2018 年 9 月)
    • 五、Android Gradle Plugin V3.1.0(2018 年 3 月)
    • 六、Android Gradle Plugin V3.0.0(2017 年 10 月)
    • 七、Android Gradle Plugin V2.3.0(2017 年 2 月)
  • 3、Gradle 構建核心流程解析
    • 一、LoadSettings
    • 二、Configure
    • 三、TaskGraph
    • 四、RunTasks
    • 五、Finished
  • 4、關於 Gradle 中依賴實現的原理
    • 一、經過 MethodMissing 機制,間接地調用 DefaultDependencyHandler 的 add 方法去添加依賴。
    • 二、不一樣的依賴聲明,實際上是由不一樣的轉換器進行轉換的。
    • 三、Project 依賴的本質是 Artifacts 依賴,也即 產物依賴。
    • 四、什麼是 Gradle 中的 Configuration?
    • 五、Task 是如何發佈本身 Artifacts 的?
  • 5、AppPlugin 構建流程
    • 一、準備工做
    • 二、configureProject 配置項目
    • 三、configureExtension 配置 Extension
    • 四、TaskManager#createTasksBeforeEvaluate 建立不依賴 flavor 的 task
    • 五、BasePlugin#createAndroidTasks 建立構建 task
  • 6、assembleDebug 打包流程淺析
    • 一、Android 打包流程回顧
    • 二、assmableDebug 打包流程淺析
  • 7、重要 Task 實現源碼分析
    • 一、資源處理相關 Task
    • 二、將 Class 文件打包成 Dex 文件的過程
  • 8、總結
    • 最後的最後

5、AppPlugin 構建流程

爲了可以查看 Android Gradle Plugin 與 Gradle 的源碼,咱們須要在項目中添加 android gradle plugin 依賴,以下所示:java

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

衆所周知,咱們可以將一個 moudle 構建爲一個 Android 項目是因爲在 build.gradle 中配置了以下的插件應用代碼:android

apply plugin: 'com.android.application'
複製代碼

當執行到 apply plugin: 'com.android.application' 這行配置時,也就開始了 AppPlugin 的構建流程,下面咱們就來分析下 AppPlugin 的構建流程。git

'com.android.application' 對應的插件 properties 爲 'com.android.internal.application',內部標明的插件實現類以下所示:github

implementation-class=com.android.build.gradle.internal.plugins.AppPlugin 複製代碼

AppPlugin 中的關鍵實現代碼以下所示:緩存

/** Gradle plugin class for 'application' projects, applied on the base application module */
public class AppPlugin extends AbstractAppPlugin {
    ...

    // 應用指定的 plugin,這裏是一個空實現
    @Override
    protected void pluginSpecificApply(@NonNull Project project) {
    }

    ...

    // 獲取一個擴展類:應用 application plugin 都會提供一個與之對應的 android extension
    @Override
    @NonNull
    protected Class<? extends AppExtension> getExtensionClass() {
        return BaseAppModuleExtension.class;
    }

    ...
}
複製代碼

那 apply 方法是在什麼地方被調用的呢?markdown

首先,咱們梳理下 AppPlugin 的繼承關係,以下所示:閉包

AppPlugin => AbstractAppPlugin => BasePlugin
複製代碼

而 apply 方法就在 BasePlugin 類中,BasePlugin 是一個應用於全部 Android Plugin 的基類,在 apply 方法中會預先進行一些準備工做架構

一、準備工做

當編譯器執行到 apply plugin 這行 groovy 代碼時,gradle 便會最終回調 BasePlugin 基類 的 apply 方法,以下所示:app

@Override
public final void apply(@NonNull Project project) {
    CrashReporting.runAction(
            () -> {
                basePluginApply(project);
                pluginSpecificApply(project);
            });
}
複製代碼

在 apply 方法中調用了 basePluginApply 方法,其源碼以下所示:

private void basePluginApply(@NonNull Project project) {
       
        ...
        
        // 一、DependencyResolutionChecks 會檢查並確保在配置階段不去解析依賴。
        DependencyResolutionChecks.registerDependencyCheck(project, projectOptions);

        // 二、應用一個 AndroidBasePlugin,目的是爲了讓其餘插件做者區分當前應用的是一個 Android 插件。
        project.getPluginManager().apply(AndroidBasePlugin.class);

        // 三、檢查 project 路徑是否有錯誤,發生錯誤則拋出 StopExecutionException 異常。
        checkPathForErrors();
        
        // 四、檢查子 moudle 的結構:目前版本會檢查 2 個模塊有沒有相同的標識(組+名稱),若是有則拋出 StopExecutionException 異常。(組件化在不一樣的 moudle 中須要給資源加 prefix 前綴)
        checkModulesForErrors();

        // 五、插件初始化,必須當即執行。此外,須要注意,Gradle Deamon 永遠不會同時執行兩個構建流程。
        PluginInitializer.initialize(project);
        
        // 六、初始化用於記錄構建過程當中配置信息的工廠實例 ProcessProfileWriterFactory
        RecordingBuildListener buildListener = ProfilerInitializer.init(project, projectOptions);
        
        // 七、給 project 設置 android plugin version、插件類型、插件生成器、project 選項
        ProcessProfileWriter.getProject(project.getPath())
                .setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION)
                .setAndroidPlugin(getAnalyticsPluginType())
                .setPluginGeneration(GradleBuildProject.PluginGeneration.FIRST)
                .setOptions(AnalyticsUtil.toProto(projectOptions));

        // 配置工程
        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
                project.getPath(),
                null,
                this::configureProject);

        // 配置 Extension
        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
                project.getPath(),
                null,
                this::configureExtension);

        // 建立 Tasks
        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
                project.getPath(),
                null,
                this::createTasks);
    }
複製代碼

能夠看到,前 4 個步驟都是一些檢查操做,然後 3 個步驟則是對插件進行初始化與配置。咱們 梳理下 準備工程 中的任務,以下所示:

  • 一、插件檢查操做
    • 1)、使用 DependencyResolutionChecks 類去檢查並確保在配置階段不去解析依賴
    • 2)、應用一個 AndroidBasePlugin,目的是爲了讓其餘插件做者區分當前應用的是一個 Android 插件
    • 3)、檢查 project 路徑是否有錯誤,發生錯誤則拋出 StopExecutionException 異常
    • 4)、檢查子 moudle 的結構,目前版本會檢查 2 個模塊有沒有相同的標識(組 + 名稱),若是有則拋出 StopExecutionException 異常。(聯想到組件化在不一樣的 moudle 中須要給資源加 prefix 前綴)
  • 二、對插件進行初始化與配置相關信息
    • 1)、當即執行插件初始化
    • 2)、初始化 用於記錄構建過程當中配置信息的工廠實例 ProcessProfileWriterFactory
    • 3)、給 project 設置 android plugin version、插件類型、插件生成器、project 選項

二、configureProject 配置項目

Plugin 的準備工程完成以後,就會執行 BasePlugin 中的 configureProject 方法進行項目的配置了,其源碼以下所示:

private void configureProject() {
        
        ...
        
        // 一、建立 DataBindingBuilder 實例。
        dataBindingBuilder = new DataBindingBuilder();
        dataBindingBuilder.setPrintMachineReadableOutput(
                SyncOptions.getErrorFormatMode(projectOptions) == ErrorFormatMode.MACHINE_PARSABLE);

        // 二、強制使用不低於當前所支持的最小插件版本,不然會拋出異常。
        GradlePluginUtils.enforceMinimumVersionsOfPlugins(project, syncIssueHandler);

        // 三、應用 Java Plugin。
        project.getPlugins().apply(JavaBasePlugin.class);

        // 四、若是啓動了 構建緩存 選項,則會建立 buildCache 實例以便後面能重用緩存。
        @Nullable
        FileCache buildCache = BuildCacheUtils.createBuildCacheIfEnabled(project, projectOptions);

        // 五、這個回調將會在整個 project 執行完成以後執行(注意不是在當前 moudle 執行完成以後執行),由於每個 project 都會調用此回調, 因此它可能會執行屢次。
        // 在整個 project 構建完成以後,會進行資源回收、緩存清除並關閉在此過程當中全部啓動的線程池組件。
        gradle.addBuildListener(
                new BuildAdapter() {
                    @Override
                    public void buildFinished(@NonNull BuildResult buildResult) {
                        // Do not run buildFinished for included project in composite build.
                        if (buildResult.getGradle().getParent() != null) {
                            return;
                        }
                        ModelBuilder.clearCaches();
                        Workers.INSTANCE.shutdown();
                        sdkComponents.unload();
                        SdkLocator.resetCache();
                        ConstraintHandler.clearCache();
                        CachedAnnotationProcessorDetector.clearCache();
                        threadRecorder.record(
                                ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
                                project.getPath(),
                                null,
                                () -> {
                                    if (!projectOptions.get(
                                            BooleanOption.KEEP_SERVICES_BETWEEN_BUILDS)) {
                                        WorkerActionServiceRegistry.INSTANCE
                                                .shutdownAllRegisteredServices(
                                                        ForkJoinPool.commonPool());
                                    }
                                    Main.clearInternTables();
                                });
                        DeprecationReporterImpl.Companion.clean();
                    }
                });
        
        ...
}
複製代碼

最後,咱們梳理下 configureProject 中所執行的 五項主要任務,以下所示:

  • 1)、建立 DataBindingBuilder 實例
  • 2)、強制使用不低於當前所支持的最小插件版本,不然會拋出異常
  • 3)、應用 Java Plugin
  • 4)、若是啓動了 構建緩存 選項,則會建立 buildCache 實例以便後續能重用緩存
  • 5)、這個回調將會在整個 project 執行完成以後執行(注意不是在當前 moudle 執行完成以後執行),由於每個 project 都會調用此回調, 因此它可能會執行屢次。最後,在整個 project 構建完成以後,會進行資源回收、緩存清除並關閉在此過程當中全部啓動的線程池組件

三、configureExtension 配置 Extension

而後,咱們看到 BasePlugin 的 configureExtension 方法,其核心源碼以下所示:

private void configureExtension() {
        
        // 一、建立盛放 buildType、productFlavor、signingConfig 的容器實例。
        ...

        final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs =
                project.container(BaseVariantOutput.class);

        // 二、建立名爲 buildOutputs 的擴展屬性配置。
        project.getExtensions().add("buildOutputs", buildOutputs);

        ...

        // 三、建立 android DSL 閉包。
        extension =
                createExtension(
                        project,
                        projectOptions,
                        globalScope,
                        buildTypeContainer,
                        productFlavorContainer,
                        signingConfigContainer,
                        buildOutputs,
                        sourceSetManager,
                        extraModelInfo);

        // 四、給全局域設置建立好的 android DSL 閉包。
        globalScope.setExtension(extension);

        // 五、建立一個 ApplicationVariantFactory 實例,以用於生產 APKs。
        variantFactory = createVariantFactory(globalScope);

        // 六、建立一個 ApplicationTaskManager 實例,負責爲 Android 應用工程去建立 Tasks。
        taskManager =
                createTaskManager(
                        globalScope,
                        project,
                        projectOptions,
                        dataBindingBuilder,
                        extension,
                        variantFactory,
                        registry,
                        threadRecorder);

        // 七、建立一個 VariantManager 實例,用於去建立與管理 Variant。
        variantManager =
                new VariantManager(
                        globalScope,
                        project,
                        projectOptions,
                        extension,
                        variantFactory,
                        taskManager,
                        sourceSetManager,
                        threadRecorder);
                        
         // 八、將 whenObjectAdded callbacks 映射到 singingConfig 容器之中。
        signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig);

        // 九、若是不是 DynamicFeature(負責添加一個可選的 APK 模塊),則會初始化一個 debug signingConfig DSL 對象並設置給默認的 buildType DSL。
        buildTypeContainer.whenObjectAdded(
                buildType -> {
                    if (!this.getClass().isAssignableFrom(DynamicFeaturePlugin.class)) {
                        SigningConfig signingConfig =
                                signingConfigContainer.findByName(BuilderConstants.DEBUG);
                        buildType.init(signingConfig);
                    } else {
                        // initialize it without the signingConfig for dynamic-features.
                        buildType.init();
                    }
                    variantManager.addBuildType(buildType);
                });

        // 十、將 whenObjectAdded callbacks 映射到 productFlavor 容器之中。
        productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor);

        // 十一、將 whenObjectRemoved 映射在容器之中,當 whenObjectRemoved 回調執行時,會拋出 UnsupportedAction 異常。
        signingConfigContainer.whenObjectRemoved(
                new UnsupportedAction("Removing signingConfigs is not supported."));
        buildTypeContainer.whenObjectRemoved(
                new UnsupportedAction("Removing build types is not supported."));
        productFlavorContainer.whenObjectRemoved(
                new UnsupportedAction("Removing product flavors is not supported."));

        // 十二、按順序依次建立 signingConfig debug、buildType debug、buildType release 類型的 DSL。
        variantFactory.createDefaultComponents(
                buildTypeContainer, productFlavorContainer, signingConfigContainer);
}
複製代碼

最後,咱們梳理下 configureExtension 中的任務,以下所示:

  • 1)、建立盛放 buildType、productFlavor、signingConfig 的容器實例。
  • 2)、建立名爲 buildOutputs 的擴展屬性配置。
  • 3)、建立 android DSL 閉包。
  • 4)、給全局域設置建立好的 android DSL 閉包。
  • 5)、建立一個 ApplicationVariantFactory 實例,以用於生產 APKs。
  • 6)、建立一個 ApplicationTaskManager 實例,負責爲 Android 應用工程去建立 Tasks。
  • 7)、建立一個 VariantManager 實例,用於去建立與管理 Variant。
  • 8)、將 whenObjectAdded callbacks 映射到 singingConfig 容器之中。
  • 9)、將 whenObjectAdded callbacks 映射到 buildType 容器之中。若是不是 DynamicFeature(負責添加一個可選的 APK 模塊),則會初始化一個 debug signingConfig DSL 對象並設置給默認的 buildType DSL。
  • 10)、將 whenObjectAdded callbacks 映射到 productFlavor 容器之中。
  • 11)、將 whenObjectRemoved 映射在容器之中,當 whenObjectRemoved 回調執行時,會拋出 UnsupportedAction 異常。
  • 12)、按順序依次建立 signingConfig debug、buildType debug、buildType release 類型的 DSL。

其中 最核心的幾項處理能夠概括爲以下 四點

  • 1)、建立 AppExtension,即 build.gradle 中的 android DSL
  • 2)、依次建立應用的 variant 工廠、Task 管理者,variant 管理者
  • 3)、註冊 新增/移除配置 的 callback,依次包括 signingConfig,buildType,productFlavor
  • 4)、依次建立默認的 debug 簽名、建立 debug 和 release 兩個 buildType

在 BasePlugin 的 apply 方法最後,調用了 createTasks 方法來建立 Tasks,該方法以下所示:

private void createTasks() {
        // 一、在 evaluate 以前建立 Tasks
        threadRecorder.record(
                ExecutionType.TASK_MANAGER_CREATE_TASKS,
                project.getPath(),
                null,
                () -> taskManager.createTasksBeforeEvaluate());

        // 二、建立 Android Tasks
        project.afterEvaluate(
                CrashReporting.afterEvaluate(
                        p -> {
                            sourceSetManager.runBuildableArtifactsActions();

                            threadRecorder.record(
                                    ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
                                    project.getPath(),
                                    null,
                                    this::createAndroidTasks);
                        }));
}
複製代碼

能夠看到,createTasks 分爲兩種 Task 的建立方式,即 createTasksBeforeEvaluate 與 createAndroidTasks

下面,咱們來詳細分析下其實現過程。

四、TaskManager#createTasksBeforeEvaluate 建立不依賴 flavor 的 task

TaskManager 的 createTasksBeforeEvaluate 方法給 Task 容器中註冊了一系列的 Task,包括 uninstallAllTask、deviceCheckTask、connectedCheckTask、preBuild、extractProguardFiles、sourceSetsTask、assembleAndroidTest、compileLintTask 等等

五、BasePlugin#createAndroidTasks 建立構建 task

在 BasePlugin 的 createAndroidTasks 方法中主要 是生成 flavors 相關數據,並根據 flavor 建立與之對應的 Task 實例並註冊進 Task 容器之中。其核心源碼以下所示:

@VisibleForTesting
    final void createAndroidTasks() {
    
    // 一、CompileSdkVersion、插件配置衝突檢測(如 JavaPlugin、retrolambda)。
    
    // 建立一些基礎或通用的 Tasks。
    
    // 二、將 Project Path、CompileSdk、BuildToolsVersion
    、Splits、KotlinPluginVersion、FirebasePerformancePluginVersion 等信息寫入 Project 的配置之中。
    ProcessProfileWriter.getProject(project.getPath())
                .setCompileSdk(extension.getCompileSdkVersion())
                .setBuildToolsVersion(extension.getBuildToolsRevision().toString())
                .setSplits(AnalyticsUtil.toProto(extension.getSplits()));
                
    String kotlinPluginVersion = getKotlinPluginVersion();
        if (kotlinPluginVersion != null) {
            ProcessProfileWriter.getProject(project.getPath())
                    .setKotlinPluginVersion(kotlinPluginVersion);
        }
        AnalyticsUtil.recordFirebasePerformancePluginVersion(project);
                
    // 三、建立應用的 Tasks。
    List<VariantScope> variantScopes = variantManager.createAndroidTasks();

    // 建立一些基礎或通用的 Tasks 與作一些通用的處理。
    
}
複製代碼

在 createAndroidTasks 除了建立一些基礎或通用的 Tasks 與作一些通用的處理以外, 主要作了三件事,以下所示:

  • 1)、CompileSdkVersion、插件配置衝突檢測(如 JavaPlugin、retrolambda 插件)
  • 2)、將 Project Path、CompileSdk、BuildToolsVersion、Splits、KotlinPluginVersion、FirebasePerformancePluginVersion 等信息寫入 Project 的配置之中
  • 3)、建立應用的 Tasks

咱們須要 重點關注 variantManager 的 createAndroidTasks 方法,去核心源碼以下所示:

/** Variant/Task creation entry point. */
    public List<VariantScope> createAndroidTasks() {
       
        ...
       
        // 一、建立工程級別的測試任務。
        taskManager.createTopLevelTestTasks(!productFlavors.isEmpty());

        // 二、遍歷全部 variantScope,爲其變體數據建立對應的 Tasks。
        for (final VariantScope variantScope : variantScopes) {
            createTasksForVariantData(variantScope);
        }

        // 三、建立報告相關的 Tasks。
        taskManager.createReportTasks(variantScopes);

        return variantScopes;
    }
複製代碼

能夠看到,在 createAndroidTasks 方法中有 三項處理,以下所示:

  • 1)、建立工程級別的測試任務
  • 2)、遍歷全部的 variantScope,爲其變體數據建立對應的 Tasks
  • 3)、建立報告相關的 Tasks

接着,咱們繼續看看 createTasksForVariantData 方法是如何爲每個指定的 Variant 類型建立對應的 Tasks 的,其核心源碼以下所示:

// 爲每個指定的 Variant 類型建立與之對應的 Tasks
public void createTasksForVariantData(final VariantScope variantScope) {
        final BaseVariantData variantData = variantScope.getVariantData();
        final VariantType variantType = variantData.getType();
        final GradleVariantConfiguration variantConfig = variantScope.getVariantConfiguration();

        // 一、建立 Assemble Task。
        taskManager.createAssembleTask(variantData);
        
        // 二、若是 variantType 是 base moudle,則會建立相應的 bundle Task。須要注意的是,base moudle 是指包含功能的 moudle,而用於 test 的 moudle 則是不包含功能的。
        if (variantType.isBaseModule()) {
            taskManager.createBundleTask(variantData);
        }

        // 三、若是 variantType 是一個 test moudle(其做爲一個 test 的組件),則會建立相應的 test variant。
        if (variantType.isTestComponent()) {
        
            // 1)、將 variant-specific, build type multi-flavor、defaultConfig 這些依賴添加到當前的 variantData 之中。
            ...
            
            // 2)、若是支持渲染腳本,則添加渲染腳本的依賴。
            if (testedVariantData.getVariantConfiguration().getRenderscriptSupportModeEnabled()) {
                project.getDependencies()
                        .add(
                                variantDep.getCompileClasspath().getName(),
                                project.files(
                                        globalScope
                                                .getSdkComponents()
                                                .getRenderScriptSupportJarProvider()));
            }
        
            // 3)、若是當前 Variant 會輸出一個 APK,即當前是執行的一個 Android test(通常用來進行 UI 自動化測試),則會建立相應的 AndroidTestVariantTask。
            if (variantType.isApk()) { // ANDROID_TEST
                if (variantConfig.isLegacyMultiDexMode()) {
                    String multiDexInstrumentationDep =
                            globalScope.getProjectOptions().get(BooleanOption.USE_ANDROID_X)
                                    ? ANDROIDX_MULTIDEX_MULTIDEX_INSTRUMENTATION
                                    : COM_ANDROID_SUPPORT_MULTIDEX_INSTRUMENTATION;
                    project.getDependencies()
                            .add(
                                    variantDep.getCompileClasspath().getName(),
                                    multiDexInstrumentationDep);
                    project.getDependencies()
                            .add(
                                    variantDep.getRuntimeClasspath().getName(),
                                    multiDexInstrumentationDep);
                }

                taskManager.createAndroidTestVariantTasks(
                        (TestVariantData) variantData,
                        variantScopes
                                .stream()
                                .filter(TaskManager::isLintVariant)
                                .collect(Collectors.toList()));
            } else { // UNIT_TEST
                // 4)、不然說明該 Test moudle 是用於執行單元測試的,則會建立 UnitTestVariantTask。 taskManager.createUnitTestVariantTasks((TestVariantData) variantData);
            }

        } else {
            // 四、若是不是一個 Test moudle,則會調用 ApplicationTaskManager 的 createTasksForVariantScope 方法。
            taskManager.createTasksForVariantScope(
                    variantScope,
                    variantScopes
                            .stream()
                            .filter(TaskManager::isLintVariant)
                            .collect(Collectors.toList()));
        }
}
複製代碼

在 createTasksForVariantData 方法中爲每個指定的 Variant 類型建立了與之對應的 Tasks,該方法的處理邏輯以下所示:

  • 一、建立 Assemble Task
  • 二、若是 variantType 是 base moudle,則會建立相應的 bundle Task。須要注意的是,base moudle 是指包含功能的 moudle,而用於 test 的 moudle 則是不包含功能的
  • 三、若是 variantType 是一個 test moudle(其做爲一個 test 的組件),則會建立相應的 test variant
    • 1)、將 variant-specific, build type multi-flavor、defaultConfig 這些依賴添加到當前的 variantData 之中
    • 2)、若是支持渲染腳本,則添加渲染腳本的依賴
    • 3)、若是當前 Variant 會輸出一個 APK,即當前是執行的一個 Android test(通常用來進行 UI 自動化測試),則會建立相應的 AndroidTestVariantTask
    • 4)、不然說明該 Test moudle 是用於執行單元測試的,則會建立 UnitTestVariantTask
  • 四、若是不是一個 Test moudle,則會調用 ApplicationTaskManager 的 createTasksForVariantScope 方法

最終,會執行到 ApplicationTaskManager 的 createTasksForVariantScope 方法,在這個方法裏面建立了適用於應用構建的一系列 Tasks

下面,咱們就經過 assembleDebug 的打包流程來分析一下這些 Tasks。

6、assembleDebug 打包流程淺析

在對 assembleDebug 構建過程當中的一系列 Task 分析以前,咱們須要先回顧一下 Android 的打包流程(對這塊很是熟悉的同窗能夠跳過)。

一、Android 打包流程回顧

Android 官方的編譯打包流程圖以下所示:

比較粗略的打包流程可簡述爲以下 四個步驟

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

爲了 瞭解更多打包過程當中的細節,咱們須要查看更加詳細的舊版 APK 打包流程圖 ,以下圖所示:

比較詳細的打包流程可簡述爲以下 八個步驟

  • 一、首先,.aidl(Android Interface Description Language)文件須要經過 aidl 工具轉換成編譯器可以處理的 Java 接口文件
  • 二、同時,資源文件(包括 AndroidManifest.xml、佈局文件、各類 xml 資源等等)將被 AAPT(Asset Packaging Tool)(Android Gradle Plugin 3.0.0 及以後使用 AAPT2 替代了 AAPT)處理爲最終的 resources.arsc,並生成 R.java 文件以保證源碼編寫時能夠方便地訪問到這些資源
  • 三、而後,經過 Java Compiler 編譯 R.java、Java 接口文件、Java 源文件,最終它們會統一被編譯成 .class 文件
  • 四、由於 .class 並非 Android 系統所能識別的格式,因此還須要經過 dex 工具將它們轉化爲相應的 Dalvik 字節碼(包含壓縮常量池以及清除冗餘信息等工做)。這個過程當中還會加入應用所依賴的全部 「第三方庫」
  • 五、下一步,經過 ApkBuilder 工具將資源文件、DEX 文件打包生成 APK 文件
  • 六、接着,系統將上面生成的 DEX、資源包以及其它資源經過 apkbuilder 生成初始的 APK 文件包
  • 七、而後,經過簽名工具 Jarsigner 或者其它簽名工具對 APK 進行簽名獲得簽名後的 APK。若是是在 Debug 模式下,簽名所用的 keystore 是系統自帶的默認值,不然咱們須要提供本身的私鑰以完成簽名過程
  • 八、最後,若是是正式版的 APK,還會利用 ZipAlign 工具進行對齊處理,以提升程序的加載和運行速度。而對齊的過程就是將 APK 文件中全部的資源文件距離文件的起始位置都偏移4字節的整數倍,這樣經過 mmap 訪問 APK 文件的速度會更快,而且會減小其在設備上運行時的內存佔用

至此,咱們已經瞭解了整個 APK 編譯和打包的流程。

那麼,爲何 XML 資源文件要從文本格式編譯成二進制格式?

主要基於如下 兩點緣由

  • 一、空間佔用更小由於全部 XML 元素的標籤、屬性名稱、屬性值和內容所涉及到的字符串都會被統一收集到一個字符串資源池中,而且會去重。有了這個字符串資源池,原來使用字符串的地方就會被替換成一個索引到字符串資源池的整數值,從而能夠減小文件的大小

  • 二、解析效率更高二進制格式的 XML 文件解析速度更快。這是因爲二進制格式的 XML 元素裏面再也不包含有字符串值,所以就避免了進行字符串解析,從而提升瞭解析效率

而 Android 資源管理框架又是如何快速定位到最匹配資源的?

主要基於兩個文件,以下所示:

  • 一、資源 ID 文件 R.java賦予每個非 assets 資源一個 ID 值,這些 ID 值以常量的形式定義在 R.java 文件中
  • 二、資源索引表 resources.arsc用來描述那些具備 ID 值的資源的配置信息

二、assmableDebug 打包流程淺析

咱們能夠經過下面的命令來獲取打包一個 Debug APK 所須要的執行的 Task,以下所示:

quchao@quchaodeMacBook-Pro CustomPlugin % ./gradlew app:assembleDebug --console=plain

...

> Task :app:preBuild UP-TO-DATE
> Task :app:preDebugBuild UP-TO-DATE
> Task :app:generateDebugBuildConfig
> Task :app:javaPreCompileDebug
> Task :app:mainApkListPersistenceDebug
> Task :app:generateDebugResValues
> Task :app:createDebugCompatibleScreenManifests
> Task :app:extractDeepLinksDebug
> Task :app:compileDebugAidl NO-SOURCE
> Task :app:compileDebugRenderscript NO-SOURCE
> Task :app:generateDebugResources
> Task :app:processDebugManifest
> Task :app:mergeDebugResources
> Task :app:processDebugResources
> Task :app:compileDebugJavaWithJavac
> Task :app:compileDebugSources
> Task :app:mergeDebugShaders
> Task :app:compileDebugShaders
> Task :app:generateDebugAssets
> Task :app:mergeDebugAssets
> Task :app:processDebugJavaRes NO-SOURCE
> Task :app:checkDebugDuplicateClasses
> Task :app:dexBuilderDebug
> Task :app:mergeLibDexDebug
> Task :app:mergeDebugJavaResource
> Task :app:mergeDebugJniLibFolders
> Task :app:validateSigningDebug
> Task :app:mergeProjectDexDebug
> Task :app:mergeDebugNativeLibs
> Task :app:stripDebugDebugSymbols
> Task :app:desugarDebugFileDependencies
> Task :app:mergeExtDexDebug
> Task :app:packageDebug
> Task :app:assembleDebug
複製代碼

在 TaskManager 中,主要有兩種方法用來去建立 Task,它們分別爲 createTasksBeforeEvaluate 方法與 createTasksForVariantScope 方法。須要注意的是,createTasksForVariantScope 方法是一個抽象方法,其具體的建立 Tasks 的任務分發給了 TaskManager 的子類進行處理,其中最多見的子類要數 ApplicationTaskManager 了,它就是在 Android 應用程序中用於建立 Tasks 的 Task 管理者

其中,打包流程中的大部分 tasks 都在這個目錄之下:

com.android.build.gradle.internal.tasks

下面,咱們看看 assembleDebug 打包流程中所需的各個 Task 所對應的實現類與含義,以下表所示:

Task 對應實現類 做用
preBuild AppPreBuildTask 預先建立的 task,用於作一些 application Variant 的檢查
preDebugBuild 與 preBuild 區別是這個 task 是用於在 Debug 的環境下的一些 Vrariant 檢查
generateDebugBuildConfig GenerateBuildConfig 生成與構建目標相關的 BuildConfig 類
javaPreCompileDebug JavaPreCompileTask 用於在 Java 編譯以前執行必要的 action
mainApkListPersistenceDebug MainApkListPersistence 用於持久化 APK 數據
generateDebugResValues GenerateResValues 生成 Res 資源類型值
createDebugCompatibleScreenManifests CompatibleScreensManifest 生成具備給定屏幕密度與尺寸列表的 (兼容屏幕)節點清單
extractDeepLinksDebug ExtractDeepLinksTask 用於抽取一系列 DeepLink(深度連接技術,主要應用場景是經過Web頁面直接調用Android原生app,而且把須要的參數經過Uri的形式,直接傳遞給app,節省用戶的註冊成本)
compileDebugAidl AidlCompile 編譯 AIDL 文件
compileDebugRenderscript RenderscriptCompile 編譯 Renderscript 文件
generateDebugResources 在 TaskManager.createAnchorTasks 方法中經過 taskFactory.register(taskName)的方式註冊一個 task 空 task,錨點
processDebugManifest ProcessApplicationManifest 處理 manifest 文件
mergeDebugResources MergeResources 使用 AAPT2 合併資源文件
processDebugResources ProcessAndroidResources 用於處理資源並生成 R.class 文件
compileDebugJavaWithJavac JavaCompileCreationAction(這裏是一個 Action,從 gradle 源碼中能夠看到從 TaskFactory 中註冊一個 Action 能夠獲得與之對應的 Task,所以,Task 即 Action,Action 即 Task) 用於執行 Java 源碼的編譯
compileDebugSources 在 TaskManager.createAnchorTasks 方法中經過 taskFactory.register(taskName)的方式註冊一個 task 空 task,錨點使用
mergeDebugShaders MergeSourceSetFolders.MergeShaderSourceFoldersCreationAction 合併 Shader 文件
compileDebugShaders ShaderCompile 編譯 Shaders
generateDebugAssets 在 TaskManager.createAnchorTasks 方法中經過 taskFactory.register(taskName)的方式註冊一個 task 空 task,錨點
mergeDebugAssets MergeSourceSetFolders.MergeAppAssetCreationAction 合併 assets 文件
processDebugJavaRes ProcessJavaResConfigAction 處理 Java Res 資源
checkDebugDuplicateClasses CheckDuplicateClassesTask 用於檢測工程外部依賴,確保不包含重複類
dexBuilderDebug DexArchiveBuilderTask 用於將 .class 文件轉換成 dex archives,即 DexArchive,Dex 存檔,能夠經過 addFile 添加一個 DEX 文件
mergeLibDexDebug DexMergingTask.DexMergingAction.MERGE_LIBRARY_PROJECT 僅僅合併庫工程中的 DEX 文件
mergeDebugJavaResource MergeJavaResourceTask 合併來自多個 moudle 的 Java 資源
mergeDebugJniLibFolders MergeSourceSetFolders.MergeJniLibFoldersCreationAction 以合適的優先級合併 JniLibs 源文件夾
validateSigningDebug ValidateSigningTask 用於檢查當前 Variant 的簽名配置中是否存在密鑰庫文件,若是當前密鑰庫默認是 debug keystore,即便它不存在也會進行相應的建立
mergeProjectDexDebug DexMergingTask.DexMergingAction.MERGE_PROJECT 僅僅合併工程的 DEX 文件
mergeDebugNativeLibs MergeNativeLibsTask 從多個 moudle 中合併 native 庫
stripDebugDebugSymbols StripDebugSymbolsTask 從 Native 庫中移除 Debug 符號。
desugarDebugFileDependencies DexFileDependenciesTask 處理 Dex 文件的依賴關係
mergeExtDexDebug DexMergingTask.DexMergingAction.MERGE_EXTERNAL_LIBS 僅僅用於合併外部庫的 DEX 文件
packageDebug PackageApplication 打包 APK
assembleDebug Assemble 空 task,錨點使用

目前,在 Gradle Plugin 中主要有三種類型的 Task,以下所示:

  • 1)、增量 Task繼承於 NewIncrementalTask 這個增量 Task 基類,須要重寫 doTaskAction 抽象方法實現增量功能
  • 2)、非增量 Task繼承於 NonIncrementalTask 這個非增量 Task 基類,重寫 doTaskAction 抽象方法實現全量更新功能
  • 3)、Transform Task咱們編寫的每個自定義 Transform 會在調用 appExtension.registerTransform(new CustomTransform()) 註冊方法時將其保存到當前的 Extension 類中的 transforms 列表中,當 LibraryTaskManager/TaskManager 調用 createPostCompilationTasks(負責爲給定 Variant 建立編譯後的 task)方法時,會取出相應 Extension 中的 tranforms 列表進行遍歷,並經過 TransformManager.addTransform 方法將每個 Transform 轉換爲與之對應的 TransformTask 實例,而該方法內部具體是經過 new TransformTask.CreationAction(...) 的形式進行建立

全面瞭解了打包過程當中涉及到的一系列 Tasks 與 Task 必備的一些基礎知識以後,咱們再來對其中最重要的幾個 Task 的實現來進行詳細分析。

7、重要 Task 實現源碼分析

一、資源處理相關 Task

1)、processDebugManifest

processDebugManifest 對應的實現類爲 ProcessApplicationManifest Task,它繼承了 IncrementalTask,可是沒有實現 isIncremental 方法,所以咱們只需看其 doFullTaskAction 方法便可。

調用鏈路

processDebugManifest.dofFullTaskAction => ManifestHelperKt.mergeManifestsForApplication => ManifestMerge2.merge
複製代碼

主要流程分析

這個 task 功能主要是 用於合併全部的(包括 module 和 flavor) mainfest,其過程主要是利用 MergingReport,ManifestMerger2 和 XmlDocument 這三個實例進行處理

咱們直接關注到 ManifestMerger2.merge 方法的 merge 過程,看看具體的合併是怎樣的。其主體步驟以下所示:

一、獲取主 manifest 的信息,以作一些必要的檢查,這裏會返回一個 LoadedManifestInfo 實例。
// load the main manifest file to do some checking along the way.
LoadedManifestInfo loadedMainManifestInfo =
        load(
                new ManifestInfo(
                        mManifestFile.getName(),
                        mManifestFile,
                        mDocumentType,
                        Optional.absent() /* mainManifestPackageName*/),
                selectors,
                mergingReportBuilder);
複製代碼
二、執行 Manifest 中的系統屬性注入:將主 Manifest 中定義的某些屬性替換成 gradle 中定義的屬性,例如 package, version_code, version_name, min_sdk_versin 、target_sdk_version、max_sdk_version 等等。
// perform system property injection
performSystemPropertiesInjection(mergingReportBuilder,
        loadedMainManifestInfo.getXmlDocument());
複製代碼
/** * Perform {@link ManifestSystemProperty} injection. * @param mergingReport to log actions and errors. * @param xmlDocument the xml document to inject into. */
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);
        }
    }
}
複製代碼
三、合併 flavors 而且構建與之對應的 manifest 文件。
for (File inputFile : mFlavorsAndBuildTypeFiles) {
    mLogger.verbose("Merging flavors and build manifest %s \n", inputFile.getPath());
    LoadedManifestInfo overlayDocument =
            load(
                    new ManifestInfo(
                            null,
                            inputFile,
                            XmlDocument.Type.OVERLAY,
                            mainPackageAttribute.transform(it -> it.getValue())),
                    selectors,
                    mergingReportBuilder);
    if (!mFeatureName.isEmpty()) {
        overlayDocument =
                removeDynamicFeatureManifestSplitAttributeIfSpecified(
                        overlayDocument, mergingReportBuilder);
    }
    // 一、檢查 package 定義
    Optional<XmlAttribute> packageAttribute =
            overlayDocument.getXmlDocument().getPackage();
    // if both files declare a package name, it should be the same.
    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());
        mergingReportBuilder.addMessage(
                overlayDocument.getXmlDocument().getSourceFile(),
                MergingReport.Record.Severity.ERROR,
                message);
        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 文件中的 placeholder 替換
performPlaceHolderSubstitution(
        loadedMainManifestInfo,
        xmlDocumentOptional.get(),
        mergingReportBuilder,
        severity);
複製代碼
六、以後對最終合併後的 manifest 中的一些屬性進行一次替換,與步驟 2 相似。
七、保存 manifest 到 build/intermediates/merged_manifests/flavorName/AndroidManifest.xml,至此,已生成最終的 Manifest 文件。

2)、mergeDebugResources

mergeDebugResources 對應的是 MergeResources Task,它 使用了 AAPT2 合併資源

調用鏈路

MergeResources.doFullTaskAction => ResourceMerger.mergeData => MergedResourceWriter.end => mResourceCompiler.submitCompile => AaptV2CommandBuilder.makeCompileCommand
複製代碼

主體流程分析

MergeResources 繼承自 IncrementalTask,對於 增量 Task 來講咱們只需看以下三個方法的實現:

  • isIncremental
  • doFullTaskAction
  • doIncrementalTaskAction
一、首先查看 isIncremental 方法。
// 說明 MergeResources Task 支持增量,確定重寫了 doIncrementalTaskAction 方法
    protected boolean isIncremental() {
        return true;
    }
複製代碼
二、而後,查看 doFullTaskAction 方法,內部經過 getConfiguredResourceSets 方法獲取了 resourceSets,包括了本身的 res 和依賴庫的 res 資源以及 build/generated/res/rs。
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
複製代碼
三、建立 ResourceMerger,並使用 resourceSets 進行填充。
ResourceMerger merger = new ResourceMerger(minSdk.get());
複製代碼
四、建立 ResourceCompilationService,它使用了 aapt2。
// makeAapt 中使用 aapt2,而後返回 ResourceCompilationService 實例.
ResourceCompilationService resourceCompiler =
        getResourceProcessor(
                getAapt2FromMaven(),
                workerExecutorFacade,
                errorFormatMode,
                flags,
                processResources,
                getLogger())) {
複製代碼
五、將第 2 步獲取的 resourceSet 加入至 ResourceMerger 中。
for (ResourceSet resourceSet : resourceSets) {
    resourceSet.loadFromFiles(new LoggerWrapper(getLogger()));
    merger.addDataSet(resourceSet);
}
複製代碼
六、建立 MergedResourceWriter
MergedResourceWriter writer =
        new MergedResourceWriter(
                workerExecutorFacade,
                destinationDir,
                publicFile,
                mergingLog,
                preprocessor,
                resourceCompiler,
                getIncrementalFolder(),
                dataBindingLayoutProcessor,
                mergedNotCompiledResourcesOutputDirectory,
                pseudoLocalesEnabled,
                getCrunchPng());
複製代碼
七、調用 ResourceMerger.mergeData 方法對資源進行合併。
merger.mergeData(writer, false /*doCleanUp*/);
複製代碼
八、調用 MergedResourceWriter 的 start,ignoreItemInMerge、removeItem、addItem,end 方法,其中 item 中包括了須要處理的資源,包括 xml 和 圖片資源,每個 item 對應的文件,都會建立一個與之對應的 CompileResourceRequest 實例,並加入到 mCompileResourceRequests 這個 ConcurrentLinkedQueue 隊列中。
九、調用 mResourceCompiler.submitCompile 方法處理資源。
// MergedResourceWriter.end()
mResourceCompiler.submitCompile(
        new CompileResourceRequest(
                fileToCompile,
                request.getOutputDirectory(),
                request.getInputDirectoryName(),
                request.getInputFileIsFromDependency(),
                pseudoLocalesEnabled,
                crunchPng,
                ImmutableMap.of(),
                request.getInputFile()));
mCompiledFileMap.put(
        fileToCompile.getAbsolutePath(),
        mResourceCompiler.compileOutputFor(request).getAbsolutePath());
複製代碼

在 submitCompile 中最終會使用 AaptV2CommandBuilder.makeCompileCommand 方法生成 aapt2 命令去處理資源。

十、最後,對 doIncrementalTaskAction 的實現我這裏就不贅述了,由於增量 task 的實現過程和全量實現差別不大,僅僅是使用修改後的文件去獲取 resourceSets 。

二、將 Class 文件打包成 Dex 文件的過程

即 DexArchiveBuilderTask,它具體對應的是 DexArchiveBuilderTask,用於將 .class 文件轉換成 dex archives,即 Dex 存檔,它能夠經過 addFile 添加一個 DEX 文件。

調用鏈路

DexArchiveBuilderTask.doTaskAction => DexArchiveBuilderTaskDelegate.doProcess => DexArchiveBuilderTaskDelegate.processClassFromInput => DexArchiveBuilderTaskDelegate.convertToDexArchive -> DexArchiveBuilderTaskDelegate.launchProcessing -> DexArchiveBuilder.convert
複製代碼

主體流程分析

在 DexArchiveBuilderTask 中,對 class 的處理方式分爲兩種,一種是對 目錄下的 class 進行處理,一種是對 .jar 裏面的 class 進行處理

那麼,這裏爲何要分爲這兩種方式呢?

由於 .jar 中的 class 文件一般來講都是依賴庫,基本上不會改變,因此 gradle 在這裏就能夠實現一個緩存操做

一、convertJarToDexArchive 處理 jar

在處理 jar 包的時候,Gradle 會對 jar 包中的每個 class 文件都單獨打成一個 DEX 文件,而後再把它們放回 jar 包之中。

private fun convertJarToDexArchive(
    jarInput: File,
    outputDir: File,
    bootclasspath: ClasspathServiceKey,
    classpath: ClasspathServiceKey,
    cacheInfo: D8DesugaringCacheInfo
): List<File> {
    if (cacheInfo !== DesugaringDontCache) {
        val cachedVersion = cacheHandler.getCachedVersionIfPresent(
            jarInput, cacheInfo.orderedD8DesugaringDependencies
        )
        if (cachedVersion != null) {
            // 若是有緩存,直接使用緩存的 jar 包。
            val outputFile = getOutputForJar(jarInput, outputDir, null)
            Files.copy(
                cachedVersion.toPath(),
                outputFile.toPath(),
                StandardCopyOption.REPLACE_EXISTING
            )
            // no need to try to cache an already cached version.
            return listOf()
        }
    }
    // 若是沒有緩存,則調用 convertToDexArchive 方法去生成 dex。
    return convertToDexArchive(
        jarInput,
        outputDir,
        false,
        bootclasspath,
        classpath,
        setOf(),
        setOf()
    )
}
複製代碼
二、使用 convertToDexArchive 處理 dir 以及 jar 的後續處理

內部會調用 launchProcessing 對 dir 進行處理,代碼以下所示:

private fun launchProcessing(
    dexConversionParameters: DexArchiveBuilderTaskDelegate.DexConversionParameters,
    outStream: OutputStream,
    errStream: OutputStream,
    receiver: MessageReceiver
) {
    val dexArchiveBuilder = dexConversionParameters.getDexArchiveBuilder(
        outStream,
        errStream,
        receiver
    )
    val inputPath = dexConversionParameters.input.toPath()
    val hasIncrementalInfo =
        dexConversionParameters.input.isDirectory && dexConversionParameters.isIncremental
     // 若是 class 新增 || 修改過,就進行處理 
    fun toProcess(path: String): Boolean {
        if (!dexConversionParameters.belongsToThisBucket(path)) return false
        if (!hasIncrementalInfo) {
            return true
        }
        val resolved = inputPath.resolve(path).toFile()
        return resolved in dexConversionParameters.additionalPaths || resolved in dexConversionParameters.changedFiles
    }
    val bucketFilter = { name: String -> toProcess(name) }
    loggerWrapper.verbose("Dexing '" + inputPath + "' to '" + dexConversionParameters.output + "'")
    try {
        ClassFileInputs.fromPath(inputPath).use { input ->
            input.entries(bucketFilter).use { entries ->
                // 內部會調用 dx || d8 去生成 dex 文件
                dexArchiveBuilder.convert(
                    entries,
                    Paths.get(URI(dexConversionParameters.output)),
                    dexConversionParameters.input.isDirectory
                )
            }
        }
    } catch (ex: DexArchiveBuilderException) {
        throw DexArchiveBuilderException("Failed to process $inputPath", ex)
    }
}
複製代碼

能夠看到,在 DexArchiveBuilder 有兩個子類,它們分別以下所示:

  • D8DexArchiveBuilder調用 D8 去生成 DEX 文件
  • DxDexArchiveBuilder調用 DX 去生成 DEX 文件

咱們這裏就以 D8DexArchiveBuilder 爲例來講明其是如何調用 D8 去生成 DEX 文件的。其源碼以下所示:

@Override
public void convert( @NonNull Stream<ClassFileEntry> input, @NonNull Path output, boolean isIncremental) throws DexArchiveBuilderException {
    // 一、建立一個 D8 診斷信息處理器實例,用於發出不一樣級別的診斷信息,共分爲三類,由嚴重程度遞減分別爲:error、warning、info。
    D8DiagnosticsHandler d8DiagnosticsHandler = new InterceptingDiagnosticsHandler();
    try {
        // 二、建立一個 D8 命令構建器實例。
        D8Command.Builder builder = D8Command.builder(d8DiagnosticsHandler);
        AtomicInteger entryCount = new AtomicInteger();
        
        // 三、遍歷讀取每個類的字節數據。
        input.forEach(
                entry -> {
                    builder.addClassProgramData(
                            readAllBytes(entry), D8DiagnosticsHandler.getOrigin(entry));
                    entryCount.incrementAndGet();
                });
        if (entryCount.get() == 0) {
            // 三、若是沒有可遍歷的數據,則直接 return。這裏使用 AtomicInteger 類來實現了是否有遍歷了數據的區分處理。
            return;
        }
        OutputMode outputMode =
                isIncremental ? OutputMode.DexFilePerClassFile : OutputMode.DexIndexed;
                
        // 四、給 D8 命令構建器實例設置一系列的配置,例如 編譯模式、最小 Sdk 版本等等。
        builder.setMode(compilationMode)
                .setMinApiLevel(minSdkVersion)
                .setIntermediate(true)
                .setOutput(output, outputMode)
                .setIncludeClassesChecksum(compilationMode == compilationMode.DEBUG);
        if (desugaring) {
            builder.addLibraryResourceProvider(bootClasspath.getOrderedProvider());
            builder.addClasspathResourceProvider(classpath.getOrderedProvider());
            if (libConfiguration != null) {
                builder.addSpecialLibraryConfiguration(libConfiguration);
            }
        } else {
            builder.setDisableDesugaring(true);
        }
        
        // 五、使用 com.android.tools.r8 工具包中的 D8 類的 run 方法運行組裝後的 D8 命令。
        D8.run(builder.build(), MoreExecutors.newDirectExecutorService());
    } catch (Throwable e) {
        throw getExceptionToRethrow(e, d8DiagnosticsHandler);
    }
}
複製代碼

D8DexArchiveBuilder 的 convert 過程能夠概括爲 五個步驟,以下所示:

  • 1)、建立一個 D8 診斷信息處理器實例,用於發出不一樣級別的診斷信息,共分爲三類,由嚴重程度遞減分別爲:error、warning、info
  • 2)、建立一個 D8 命令構建器實例
  • 3)、遍歷讀取每個類的字節數據
  • 4)、給 D8 命令構建器實例設置一系列的配置,例如 編譯模式、最小 Sdk 版本等等
  • 5)、使用 com.android.tools.r8 工具包中的 D8 類的 run 方法運行組裝後的 D8 命令

8、總結

咱們再回頭看看開篇時的那一幅 Gradle 插件的總體實現架構圖,以下所示:

最後的最後

咱們能夠 根據上面這幅圖,由下而上細細地思考回憶一下,每一層的主要流程是什麼?其中涉及的一些關鍵細節具體有哪些?此時,你是否以爲已經真正地理解了 Gradle 插件架構的實現原理呢

參考連接:


一、Android Gradle Plugin V3.6.2 源碼

二、Gradle V5.6.4 源碼

三、Android Plugin DSL Reference

四、Gradle DSL Reference

五、designing-gradle-plugins

六、android-training => gradle

七、連載 | 深刻理解Gradle框架之一:Plugin, Extension, buildSrc

八、連載 | 深刻理解gradle框架之二:依賴實現分析

九、連載 | 深刻理解gradle框架之三:artifacts的發佈

十、Android Gradle Plugin 源碼解析(上)

十一、Android Gradle Plugin 源碼解析(下)

十二、Gradle 庖丁解牛(構建源頭源碼淺析)

1三、Gradle 庖丁解牛(構建生命週期核心委託對象建立源碼淺析)

很感謝您閱讀這篇文章,但願您能將它分享給您的朋友或技術羣,這對我意義重大。

但願咱們能成爲朋友,在 Github掘金上一塊兒分享知識。

相關文章
相關標籤/搜索