总结
我近来从朋侪那里网络到了2020-2021BAT 面试真题剖析,内容很多也很体系,包含了很多内容:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性题目等等,可以很好地帮助大家深刻明白Android相关知识点的原理以及面试相关知识。
这份资料把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节;还有 高级架构技术进阶脑图 帮助大家学习提拔进阶,也节省大家在网上搜刮资料的时间来学习,也可以分享给身边好友一起学习。
这里也分享给广大面试同胞们,盼望每位程序猿们都能面试成功~
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性题目
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2020面试真题剖析
网上学习资料一大堆,但如果学到的知识不成体系,遇到题目时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提拔。
需要这份体系化学习资料的朋侪,可以戳这里获取
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感爱好的新人,都接待加入我们的的圈子(技术交换、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
// 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 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 编译之前实行必要的 actionmainApkListPersistenceDebugMainApkListPersistence用于持久化 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编译 ShadersgenerateDebugAssets在 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打包 APKassembleDebugAssemble空 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();
}
}
末了送福利了,如今关注我可以获取包含源码剖析,自界说View,动画实现,架构分享等。
内容难度适中,篇幅精炼,天天只需花上十几分钟阅读即可。
大家可以跟我一起探究,有flutter—底层开辟—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿
网上学习资料一大堆,但如果学到的知识不成体系,遇到题目时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提拔。
需要这份体系化学习资料的朋侪,可以戳这里获取
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感爱好的新人,都接待加入我们的的圈子(技术交换、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
ManifestInfo libraryDocument : loadedLibraryDocuments) {
mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
xmlDocumentOptional = merge(
xmlDocumentOptional, libraryDocument, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {
return mergingReportBuilder.build();
}
}
末了送福利了,如今关注我可以获取包含源码剖析,自界说View,动画实现,架构分享等。
内容难度适中,篇幅精炼,天天只需花上十几分钟阅读即可。
大家可以跟我一起探究,有flutter—底层开辟—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿
[外链图片转存中…(img-fWO8uJgQ-1715420858412)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到题目时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提拔。
需要这份体系化学习资料的朋侪,可以戳这里获取
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感爱好的新人,都接待加入我们的的圈子(技术交换、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |