深入学习-Gradle-自动化构建技术(五)Gradle-插件架构实现原理剖析-—-下

  • 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 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 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 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对应实现类作用
preBuildAppPreBuildTask预先创建的 task,用于做一些 application Variant 的检查
preDebugBuild与 preBuild 区别是这个 task 是用于在 Debug 的环境下的一些 Vrariant 检查
generateDebugBuildConfigGenerateBuildConfig生成与构建目标相关的 BuildConfig 类
javaPreCompileDebugJavaPreCompileTask用于在 Java 编译之前执行必要的 action
mainApkListPersistenceDebugMainApkListPersistence用于持久化 APK 数据
generateDebugResValuesGenerateResValues生成 Res 资源类型值
createDebugCompatibleScreenManifestsCompatibleScreensManifest生成具有给定屏幕密度与尺寸列表的 (兼容屏幕)节点清单
extractDeepLinksDebugExtractDeepLinksTask用于抽取一系列 DeepLink(深度链接技术,主要应用场景是通过Web页面直接调用Android原生app,并且把需要的参数通过Uri的形式,直接传递给app,节省用户的注册成本)
compileDebugAidlAidlCompile编译 AIDL 文件
compileDebugRenderscriptRenderscriptCompile编译 Renderscript 文件
generateDebugResources在 TaskManager.createAnchorTasks 方法中通过 taskFactory.register(taskName)的方式注册一个 task空 task,锚点
processDebugManifestProcessApplicationManifest处理 manifest 文件
mergeDebugResourcesMergeResources使用 AAPT2 合并资源文件
processDebugResourcesProcessAndroidResources用于处理资源并生成 R.class 文件
compileDebugJavaWithJavacJavaCompileCreationAction(这里是一个 Action,从 gradle 源码中可以看到从 TaskFactory 中注册一个 Action 可以得到与之对应的 Task,因此,Task 即 Action,Action 即 Task)用于执行 Java 源码的编译
compileDebugSources在 TaskManager.createAnchorTasks 方法中通过 taskFactory.register(taskName)的方式注册一个 task空 task,锚点使用
mergeDebugShadersMergeSourceSetFolders.MergeShaderSourceFoldersCreationAction合并 Shader 文件
compileDebugShadersShaderCompile编译 Shaders
generateDebugAssets在 TaskManager.createAnchorTasks 方法中通过 taskFactory.register(taskName)的方式注册一个 task空 task,锚点
mergeDebugAssetsMergeSourceSetFolders.MergeAppAssetCreationAction合并 assets 文件
processDebugJavaResProcessJavaResConfigAction处理 Java Res 资源
checkDebugDuplicateClassesCheckDuplicateClassesTask用于检测工程外部依赖,确保不包含重复类
dexBuilderDebugDexArchiveBuilderTask用于将 .class 文件转换成 dex archives,即 DexArchive,Dex 存档,可以通过 addFile 添加一个 DEX 文件
mergeLibDexDebugDexMergingTask.DexMergingAction.MERGE_LIBRARY_PROJECT仅仅合并库工程中的 DEX 文件
mergeDebugJavaResourceMergeJavaResourceTask合并来自多个 moudle 的 Java 资源
mergeDebugJniLibFoldersMergeSourceSetFolders.MergeJniLibFoldersCreationAction以合适的优先级合并 JniLibs 源文件夹
validateSigningDebugValidateSigningTask用于检查当前 Variant 的签名配置中是否存在密钥库文件,如果当前密钥库默认是 debug keystore,即使它不存在也会进行相应的创建
mergeProjectDexDebugDexMergingTask.DexMergingAction.MERGE_PROJECT仅仅合并工程的 DEX 文件
mergeDebugNativeLibsMergeNativeLibsTask从多个 moudle 中合并 native 库
stripDebugDebugSymbolsStripDebugSymbolsTask从 Native 库中移除 Debug 符号。
desugarDebugFileDependenciesDexFileDependenciesTask处理 Dex 文件的依赖关系
mergeExtDexDebugDexMergingTask.DexMergingAction.MERGE_EXTERNAL_LIBS仅仅用于合并外部库的 DEX 文件
packageDebugPackageApplication打包 APK
assembleDebugAssemble空 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 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 v a l u e = ( s value=(%2 svalue=(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 v a l u e = ( s value=(%2 svalue=(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

最后

那我们该怎么做才能做到年薪60万+呢,对于程序员来说,只有不断学习,不断提升自己的实力。我之前有篇文章提到过,感兴趣的可以看看,到底要学习哪些知识才能达到年薪60万+。

通过职友集数据可以查看,以北京 Android 相关岗位为例,其中 【20k-30k】 薪酬的 Android 工程师,占到了整体从业者的 30.8%!

北京 Android 工程师「工资收入水平 」

今天重点内容是怎么去学,怎么提高自己的技术。

1.合理安排时间

2.找对好的系统的学习资料

3.有老师带,可以随时解决问题

4.有明确的学习路线

当然图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

来说我们只需看如下三个方法的实现:

  • isIncremental
  • doFullTaskAction

最后

那我们该怎么做才能做到年薪60万+呢,对于程序员来说,只有不断学习,不断提升自己的实力。我之前有篇文章提到过,感兴趣的可以看看,到底要学习哪些知识才能达到年薪60万+。

通过职友集数据可以查看,以北京 Android 相关岗位为例,其中 【20k-30k】 薪酬的 Android 工程师,占到了整体从业者的 30.8%!

北京 Android 工程师「工资收入水平 」

[外链图片转存中…(img-oWnBFxdq-1714661668468)]

今天重点内容是怎么去学,怎么提高自己的技术。

1.合理安排时间

2.找对好的系统的学习资料

3.有老师带,可以随时解决问题

4.有明确的学习路线

当然图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。

[外链图片转存中…(img-eg3vjvz5-1714661668468)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值