目前,Gradle 自動化技術愈來愈重要,也有許多同窗已經可以製做出本身的 Gradle 插件,可是一直有一些 「梗」 遺留在咱們腦海中,無時無刻不提醒着咱們,你真的掌握了嗎?例如,「梗1」:Gradle 插件的總體實現架構?我:...,「梗2」:Android Gradle 插件更新歷史有哪些重要優化或者改進?我:..., 「梗3」:Gradle 構建的核心流程是怎樣的?我:...,「梗4」:Gradle 中依賴實現的原理?我:..., 「梗5」:AppPlugin 構建流程?我:..., 「梗6」:assembleDebug 打包流程?我:...., 「梗7」:一些很重要 Task 實現原理可否說說?我:... 。是否有不少點並無真正地去了解過呢?html
爲了可以查看 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 個步驟則是對插件進行初始化與配置。咱們 梳理下 準備工程 中的任務
,以下所示:
插件檢查操做
對插件進行初始化與配置相關信息
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 中所執行的 五項主要任務,以下所示:
而後,咱們看到 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 中的任務,以下所示:
其中 最核心的幾項處理能夠概括爲以下 四點:
在 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 方法給 Task 容器中註冊了一系列的 Task,包括 uninstallAllTask、deviceCheckTask、connectedCheckTask、preBuild、extractProguardFiles、sourceSetsTask、assembleAndroidTest、compileLintTask 等等。
在 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 與作一些通用的處理以外, 主要作了三件事,以下所示:
咱們須要 重點關注 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 方法中有 三項處理
,以下所示:
接着,咱們繼續看看 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,該方法的處理邏輯以下所示:
最終,會執行到 ApplicationTaskManager 的 createTasksForVariantScope 方法,在這個方法裏面建立了適用於應用構建的一系列 Tasks。
下面,咱們就經過 assembleDebug 的打包流程來分析一下這些 Tasks。
在對 assembleDebug 構建過程當中的一系列 Task 分析以前,咱們須要先回顧一下 Android 的打包流程(對這塊很是熟悉的同窗能夠跳過)。
Android 官方的編譯打包流程圖以下所示:
比較粗略的打包流程可簡述爲以下 四個步驟:
爲了 瞭解更多打包過程當中的細節,咱們須要查看更加詳細的舊版 APK 打包流程圖 ,以下圖所示:
比較詳細的打包流程可簡述爲以下 八個步驟:
至此,咱們已經瞭解了整個 APK 編譯和打包的流程。
主要基於如下 兩點緣由
:
一、空間佔用更小
:由於全部 XML 元素的標籤、屬性名稱、屬性值和內容所涉及到的字符串都會被統一收集到一個字符串資源池中,而且會去重。有了這個字符串資源池,原來使用字符串的地方就會被替換成一個索引到字符串資源池的整數值,從而能夠減小文件的大小。
二、解析效率更高
:二進制格式的 XML 文件解析速度更快。這是因爲二進制格式的 XML 元素裏面再也不包含有字符串值,所以就避免了進行字符串解析,從而提升瞭解析效率。
主要基於兩個文件,以下所示:
資源 ID 文件 R.java
:賦予每個非 assets 資源一個 ID 值,這些 ID 值以常量的形式定義在 R.java 文件中。資源索引表 resources.arsc
:用來描述那些具備 ID 值的資源的配置信息。咱們能夠經過下面的命令來獲取打包一個 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,以下所示:
增量 Task
:繼承於 NewIncrementalTask 這個增量 Task 基類,須要重寫 doTaskAction 抽象方法實現增量功能。非增量 Task
:繼承於 NonIncrementalTask 這個非增量 Task 基類,重寫 doTaskAction 抽象方法實現全量更新功能。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 的實現來進行詳細分析。
processDebugManifest 對應的實現類爲 ProcessApplicationManifest Task,它繼承了 IncrementalTask,可是沒有實現 isIncremental 方法,所以咱們只需看其 doFullTaskAction 方法便可。
processDebugManifest.dofFullTaskAction => ManifestHelperKt.mergeManifestsForApplication => ManifestMerge2.merge
複製代碼
這個 task 功能主要是 用於合併全部的(包括 module 和 flavor) mainfest,其過程主要是利用 MergingReport,ManifestMerger2 和 XmlDocument 這三個實例進行處理。
咱們直接關注到 ManifestMerger2.merge 方法的 merge 過程,看看具體的合併是怎樣的。其主體步驟以下所示:
// 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);
複製代碼
// 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);
}
}
}
複製代碼
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();
}
...
}
複製代碼
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
xmlDocumentOptional = merge(
xmlDocumentOptional, libraryDocument, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {
return mergingReportBuilder.build();
}
}
複製代碼
performPlaceHolderSubstitution(
loadedMainManifestInfo,
xmlDocumentOptional.get(),
mergingReportBuilder,
severity);
複製代碼
mergeDebugResources 對應的是 MergeResources Task,它 使用了 AAPT2 合併資源。
MergeResources.doFullTaskAction => ResourceMerger.mergeData => MergedResourceWriter.end => mResourceCompiler.submitCompile => AaptV2CommandBuilder.makeCompileCommand
複製代碼
MergeResources 繼承自 IncrementalTask,對於 增量 Task 來講咱們只需看以下三個方法的實現:
isIncremental
doFullTaskAction
doIncrementalTaskAction
// 說明 MergeResources Task 支持增量,確定重寫了 doIncrementalTaskAction 方法
protected boolean isIncremental() {
return true;
}
複製代碼
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
複製代碼
ResourceMerger merger = new ResourceMerger(minSdk.get());
複製代碼
// makeAapt 中使用 aapt2,而後返回 ResourceCompilationService 實例.
ResourceCompilationService resourceCompiler =
getResourceProcessor(
getAapt2FromMaven(),
workerExecutorFacade,
errorFormatMode,
flags,
processResources,
getLogger())) {
複製代碼
for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(new LoggerWrapper(getLogger()));
merger.addDataSet(resourceSet);
}
複製代碼
MergedResourceWriter writer =
new MergedResourceWriter(
workerExecutorFacade,
destinationDir,
publicFile,
mergingLog,
preprocessor,
resourceCompiler,
getIncrementalFolder(),
dataBindingLayoutProcessor,
mergedNotCompiledResourcesOutputDirectory,
pseudoLocalesEnabled,
getCrunchPng());
複製代碼
merger.mergeData(writer, false /*doCleanUp*/);
複製代碼
// 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 命令去處理資源。
即 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 在這裏就能夠實現一個緩存操做。
在處理 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()
)
}
複製代碼
內部會調用 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 過程能夠概括爲 五個步驟
,以下所示:
咱們再回頭看看開篇時的那一幅 Gradle 插件的總體實現架構圖,以下所示:
咱們能夠 根據上面這幅圖,由下而上細細地思考回憶一下,每一層的主要流程是什麼?其中涉及的一些關鍵細節具體有哪些?此時,你是否以爲已經真正地理解了 Gradle 插件架構的實現原理呢?
一、Android Gradle Plugin V3.6.2 源碼
二、Gradle V5.6.4 源碼
三、Android Plugin DSL Reference
七、連載 | 深刻理解Gradle框架之一:Plugin, Extension, buildSrc
九、連載 | 深刻理解gradle框架之三:artifacts的發佈
十、Android Gradle Plugin 源碼解析(上)
十一、Android Gradle Plugin 源碼解析(下)
1三、Gradle 庖丁解牛(構建生命週期核心委託對象建立源碼淺析)