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

前言

成爲一名優秀的Android開發,需要一份完備的知識體系,在這裏,讓我們一起成長爲自己所想的那樣~。

五、AppPlugin 構建流程

爲了能夠查看 Android Gradle Plugin 與 Gradle 的源碼,我們需要在項目中添加 android gradle plugin 依賴,如下所示:

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

衆所周知,我們能夠將一個 moudle 構建爲一個 Android 項目是由於在 build.gradle 中配置瞭如下的插件應用代碼:

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

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

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

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 方法是在什麼地方被調用的呢?

首先,我們梳理下 AppPlugin 的繼承關係,如下所示:

AppPlugin => AbstractAppPlugin => BasePlugin
複製代碼

而 apply 方法就在 BasePlugin 類中,BasePlugin 是一個應用於所有 Android Plugin 的基類,在 apply 方法中會預先進行一些準備工作

1、準備工作

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

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

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

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

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

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

        // 5、插件初始化,必須立即執行。此外,需要注意,Gradle Deamon 永遠不會同時執行兩個構建流程。
        PluginInitializer.initialize(project);
        
        // 6、初始化用於記錄構建過程中配置信息的工廠實例 ProcessProfileWriterFactory
        RecordingBuildListener buildListener = ProfilerInitializer.init(project, projectOptions);
        
        // 7、給 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、插件檢查操作
    • 1)、使用 DependencyResolutionChecks 類去檢查並確保在配置階段不去解析依賴
    • 2)、應用一個 AndroidBasePlugin,目的是爲了讓其他插件作者區分當前應用的是一個 Android 插件
    • 3)、檢查 project 路徑是否有錯誤,發生錯誤則拋出 StopExecutionException 異常
    • 4)、檢查子 moudle 的結構,目前版本會檢查 2 個模塊有沒有相同的標識(組 + 名稱),如果有則拋出 StopExecutionException 異常。(聯想到組件化在不同的 moudle 中需要給資源加 prefix 前綴)
  • 2、對插件進行初始化與配置相關信息
    • 1)、立即執行插件初始化
    • 2)、初始化 用於記錄構建過程中配置信息的工廠實例 ProcessProfileWriterFactory
    • 3)、給 project 設置 android plugin version、插件類型、插件生成器、project 選項

2、configureProject 配置項目

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

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

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

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

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

        // 5、這個回調將會在整個 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 構建完成之後,會進行資源回收、緩存清除並關閉在此過程中所有啓動的線程池組件

3、configureExtension 配置 Extension

然後,我們看到 BasePlugin 的 configureExtension 方法,其核心源碼如下所示:

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

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

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

        ...

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

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

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

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

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

        // 9、如果不是 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);
                });

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

        // 11、將 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."));

        // 12、按順序依次創建 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() {
        // 1、在 evaluate 之前創建 Tasks
        threadRecorder.record(
                ExecutionType.TASK_MANAGER_CREATE_TASKS,
                project.getPath(),
                null,
                () -> taskManager.createTasksBeforeEvaluate());

        // 2、創建 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

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

4、TaskManager#createTasksBeforeEvaluate 創建不依賴 flavor 的 task

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

5、BasePlugin#createAndroidTasks 創建構建 task

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

 @VisibleForTesting
    final void createAndroidTasks() {
    
    // 1、CompileSdkVersion、插件配置衝突檢測(如 JavaPlugin、retrolambda)。
    
    // 創建一些基礎或通用的 Tasks。
    
    // 2、將 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);
                
    // 3、創建應用的 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() {
       
        ...
       
        // 1、創建工程級別的測試任務。
        taskManager.createTopLevelTestTasks(!productFlavors.isEmpty());

        // 2、遍歷所有 variantScope,爲其變體數據創建對應的 Tasks。
        for (final VariantScope variantScope : variantScopes) {
            createTasksForVariantData(variantScope);
        }

        // 3、創建報告相關的 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();

        // 1、創建 Assemble Task。
        taskManager.createAssembleTask(variantData);
        
        // 2、如果 variantType 是 base moudle,則會創建相應的 bundle Task。需要注意的是,base moudle 是指包含功能的 moudle,而用於 test 的 moudle 則是不包含功能的。
        if (variantType.isBaseModule()) {
            taskManager.createBundleTask(variantData);
        }

        // 3、如果 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 {
            // 4、如果不是一個 Test moudle,則會調用 ApplicationTaskManager 的 createTasksForVariantScope 方法。
            taskManager.createTasksForVariantScope(
                    variantScope,
                    variantScopes
                            .stream()
                            .filter(TaskManager::isLintVariant)
                            .collect(Collectors.toList()));
        }
}
複製代碼

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

  • 1、創建 Assemble Task
  • 2、如果 variantType 是 base moudle,則會創建相應的 bundle Task。需要注意的是,base moudle 是指包含功能的 moudle,而用於 test 的 moudle 則是不包含功能的
  • 3、如果 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
  • 4、如果不是一個 Test moudle,則會調用 ApplicationTaskManager 的 createTasksForVariantScope 方法

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

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

六、assembleDebug 打包流程淺析

在對 assembleDebug 構建過程中的一系列 Task 分析之前,我們需要先回顧一下 Android 的打包流程(對這塊非常熟悉的同學可以跳過)。

1、Android 打包流程回顧

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

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

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

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

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

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

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

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

主要基於以下 兩點原因

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

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

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

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

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

2、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 的實現來進行詳細分析。

七、重要 Task 實現源碼分析

1、資源處理相關 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 過程,看看具體的合併是怎樣的。其主體步驟如下所示:

1、獲取主 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);
複製代碼
2、執行 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);
        }
    }
}
複製代碼
3、合併 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);
    }
    // 1、檢查 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())) {
        // 2、如果 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();
    }
    ...
}
複製代碼
4、合併庫中的 manifest 文件
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
    mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
    xmlDocumentOptional = merge(
            xmlDocumentOptional, libraryDocument, mergingReportBuilder);
    if (!xmlDocumentOptional.isPresent()) {
        return mergingReportBuilder.build();
    }
}
複製代碼
5、執行 manifest 文件中的 placeholder 替換
performPlaceHolderSubstitution(
        loadedMainManifestInfo,
        xmlDocumentOptional.get(),
        mergingReportBuilder,
        severity);
複製代碼
6、之後對最終合併後的 manifest 中的一些屬性進行一次替換,與步驟 2 類似。
7、保存 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
1、首先查看 isIncremental 方法。
    // 說明 MergeResources Task 支持增量,肯定重寫了 doIncrementalTaskAction 方法
    protected boolean isIncremental() {
        return true;
    }
複製代碼
2、然後,查看 doFullTaskAction 方法,內部通過 getConfiguredResourceSets 方法獲取了 resourceSets,包括了自己的 res 和依賴庫的 res 資源以及 build/generated/res/rs。
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
複製代碼
3、創建 ResourceMerger,並使用 resourceSets 進行填充。
ResourceMerger merger = new ResourceMerger(minSdk.get());
複製代碼
4、創建 ResourceCompilationService,它使用了 aapt2。
// makeAapt 中使用 aapt2,然後返回 ResourceCompilationService 實例.
ResourceCompilationService resourceCompiler =
        getResourceProcessor(
                getAapt2FromMaven(),
                workerExecutorFacade,
                errorFormatMode,
                flags,
                processResources,
                getLogger())) {
複製代碼
5、將第 2 步獲取的 resourceSet 加入至 ResourceMerger 中。
for (ResourceSet resourceSet : resourceSets) {
    resourceSet.loadFromFiles(new LoggerWrapper(getLogger()));
    merger.addDataSet(resourceSet);
}
複製代碼
6、創建 MergedResourceWriter
MergedResourceWriter writer =
        new MergedResourceWriter(
                workerExecutorFacade,
                destinationDir,
                publicFile,
                mergingLog,
                preprocessor,
                resourceCompiler,
                getIncrementalFolder(),
                dataBindingLayoutProcessor,
                mergedNotCompiledResourcesOutputDirectory,
                pseudoLocalesEnabled,
                getCrunchPng());
複製代碼
7、調用 ResourceMerger.mergeData 方法對資源進行合併。
merger.mergeData(writer, false /*doCleanUp*/);
複製代碼
8、調用 MergedResourceWriter 的 start,ignoreItemInMerge、removeItem、addItem,end 方法,其中 item 中包括了需要處理的資源,包括 xml 和 圖片資源,每一個 item 對應的文件,都會創建一個與之對應的 CompileResourceRequest 實例,並加入到 mCompileResourceRequests 這個 ConcurrentLinkedQueue 隊列中。
9、調用 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 命令去處理資源。

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

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

即 dexBuilderDebug,它具體對應的是 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 在這裏就可以實現一個緩存操作

1、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()
    )
}
複製代碼
2、使用 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 
{
    // 1、創建一個 D8 診斷信息處理器實例,用於發出不同級別的診斷信息,共分爲三類,由嚴重程度遞減分別爲:error、warning、info。
    D8DiagnosticsHandler d8DiagnosticsHandler = new InterceptingDiagnosticsHandler();
    try {
        // 2、創建一個 D8 命令構建器實例。
        D8Command.Builder builder = D8Command.builder(d8DiagnosticsHandler);
        AtomicInteger entryCount = new AtomicInteger();
        
        // 3、遍歷讀取每一個類的字節數據。
        input.forEach(
                entry -> {
                    builder.addClassProgramData(
                            readAllBytes(entry), D8DiagnosticsHandler.getOrigin(entry));
                    entryCount.incrementAndGet();
                });
        if (entryCount.get() == 0) {
            // 3、如果沒有可遍歷的數據,則直接 return。這裏使用 AtomicInteger 類來實現了是否有遍歷了數據的區分處理。
            return;
        }
        OutputMode outputMode =
                isIncremental ? OutputMode.DexFilePerClassFile : OutputMode.DexIndexed;
                
        // 4、給 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);
        }
        
        // 5、使用 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 命令

八、總結

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

最後的最後

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

公衆號

我的公衆號 JsonChao 開通啦,如果您想第一時間獲取最新文章和最新動態,歡迎掃描關注~

參考鏈接:


1、Android Gradle Plugin V3.6.2 源碼

2、Gradle V5.6.4 源碼

3、Android Plugin DSL Reference

4、Gradle DSL Reference

5、designing-gradle-plugins

6、android-training => gradle

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

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

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

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

11、Android Gradle Plugin 源碼解析(下)

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

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

Contanct Me

● 微信:

歡迎關注我的微信:bcce5360

● 微信羣:

由於微信羣已超過 200 人,麻煩大家想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎大家加入~

About me

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

希望我們能成爲朋友,在 Github掘金上一起分享知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章