前言
公司做为游戏发行商,其中有一块业务为融合 SDK,游戏制作者接入我们的 SDK 提供母包给我们,由我们负责将各个渠道包打好给他,然后进行利益分摊。而我以前对打包细节了解的不多,于是就抽了点时间研究了下 Android 构建打包流程。
流程
首先翻看了 Android 官方文档 找到了以下一个流程图:
上图中展示了大致了打包流程,不过不够详细,比如编译流程是什么样的,最后打包流程又是怎么样的,而这些源码都会告诉我。
源码
众所周知,如果需要手动在命令行构建打包 Apk,可以使用以下几个命令:
构建全量包 |
这里以第二条命令为例,执行了上述命令后,Gradle 会在初始化、配置阶段完成后执行 assembleDebug 这个 Task,这时候第一个疑问就出现了,这个 Task 是哪里来的,难道是 Gradle 自带的?很明显并不是,它来自一个插件其 id 为 com.android.application 。
查看应用程序模块下的 build.gradle 可以发现其应用了该插件。
plugins { |
那么该插件来自于哪呢?查看根项目 build.gradle 可以发现有如下配置。
buildscript { |
显然 com.android.application 插件来自 com.android.tools.build:gradle:3.0.0 库,而该库又来自 Google 的 maven 仓库。根据自定义插件流程翻看该库源码发现该插件对应的实现类为 AppPlugin。跟进源码,当务之急是找寻到 assembleDebug 这个 Task 是在哪里创建的。
public class AppPlugin extends BasePlugin implements Plugin<Project> { |
ThreadRecorder#record 方法其实就是起个计时作用,内部会执行传入的方法。而上述三个方法前两个明显是配置相关的先行跳过,直接看看 createTasks。
void createTasks() { |
可以看到其实最终会通过 VariantManager 去创建 Task,而对于每一个 Variant 都会创建自己的 Task。
注:Variant 由 BuildType 和 ProductFlavor 组成。
public void createTasksForVariantData( |
终于找到了,其最终是通过 TaskManager 创建生成的。在 AS 右端 Gradle 窗口中寻找到 assembleDebug 鼠标悬浮会出现该 Task 的描述信息也能证明这一点。
虽然找到了该 Task 的位置,不过它本身什么也不做,那么问题又来了,为啥执行它就能打包?其实根据 Gradle 规定 Task 可以添加依赖,使得在执行该 Task 前执行其它的 Task,但是由于依赖关系错综复杂,就不一处处的找了。直接执行一下,看看具体哪些 Task 被执行了。
:app:preBuild SKIPPED |
可以看到打一次包需要执行大量的 Task,下面一个个进行分析。
preBuild
BasePlugin#createTasks
TaskManager#createTasksBeforeEvaluate
public void createTasksBeforeEvaluate(@NonNull TaskFactory tasks) { |
空实现,啥也没做,就是个锚点,任何变种构建都会执行。
preDebugBuild
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#createAnchorTasks
TaskManager#createPreBuildTasks
TaskManager#createVariantPreBuildTask
TaskManager#createDefaultPreBuildTask
protected AndroidTask<? extends DefaultTask> createDefaultPreBuildTask( |
空实现,啥也没做,就是个锚点,标志着当前变种开始构建。
compileDebugAidl
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createAidlTask
public AndroidTask<AidlCompile> createAidlTask(TaskFactory tasks, VariantScope scope) { |
具体看下 AidlCompile.ConfigAction 。
public class AidlCompile extends IncrementalTask { |
Task 执行时会首先创建一个处理器,然后遍历 main/aidl 下所有的文件,遍历到后缀为 aidl 的文件后,进行处理。
public class AidlProcessor implements DirectoryWalker.FileAction { |
处理过程也相当简单,也就是拼接参数然后执行 build-tools 下面的 aidl 工具。
compileDebugRenderscript
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createRenderscriptTask
public void createRenderscriptTask( |
具体看下 RenderscriptCompile.ConfigAction 。
public class RenderscriptCompile extends NdkTask { |
Task 执行时会首先创建一个处理器,然后遍历 src/main/rs 下所有的文件,遍历到后缀为 rs 的文件后,进行处理。
private void doMainCompilation(...) { |
处理过程也很简单,也就是拼接参数然后执行 build-tools 下面的 llvm-rs-cc 工具,执行完毕后会生成对应的 java 类,在代码中就可以使用了。
注: RenderScript 是用于在 Android 上以高性能运行计算密集型任务的框架。官方文档
checkDebugManifest
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createCheckManifestTask
public void createCheckManifestTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) { |
具体看下 CheckManifest.ConfigAction 。
public class CheckManifest extends DefaultAndroidTask { |
这个 Task 很简单,就是检查了下 AndroidManifest.xml 这个文件是否存在,不存在则抛出异常。
generateDebugBuildConfig
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createBuildConfigTask
public void createBuildConfigTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) { |
具体看下 GenerateBuildConfig.ConfigAction 。
public class GenerateBuildConfig extends BaseTask { |
Task 内部添加了以下几个字段,以及用户自定义的字段,然后生成 BuildConfig.java 。
- DEBUG
- APPLICATION_ID
- BUILD_TYPE
- FLAVOR
- VERSION_CODE
- VERSION_NAME
- FLAVOR_XXX
自定义 BuildConfig 字段方法如下:
buildTypes { |
prepareLintJar
BasePlugin#createAndroidTasks
TaskManager#configureCustomLintChecks
public void configureCustomLintChecks(@NonNull TaskFactory tasks) { |
具体看下 PrepareLintJar.ConfigAction 。
public class PrepareLintJar extends DefaultTask { |
该 Task 用于处理自定义的 Lint 规则生成的 jar 包,如果有自定义的并且只有一个那么拷贝到目标路径,如果有多个则报错。
注:自定义 Lint 教程。
generateDebugResValues
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createGenerateResValuesTask
public void createGenerateResValuesTask( |
具体看下 GenerateResValues.ConfigAction 。
public class GenerateResValues extends BaseTask { |
该 Task 逻辑也很清晰,获取到自定义的 resValue 值,然后创建 generated.xml 将自定义的资源信息写入其中。
自定义 resValue 方式如下:
buildTypes { |
最终生成的 generated.xml 文件内容如下:
|
generateDebugResources
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#createAnchorTasks
public void createAnchorTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) { |
空实现,啥也没做,就是个锚点,标志着资源处理开始了。
mergeDebugResources
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createMergeResourcesTask
TaskManager#basicCreateMergeResourcesTask
public AndroidTask<MergeResources> basicCreateMergeResourcesTask(...) { |
具体看下 MergeResources.ConfigAction 。
public class MergeResources extends IncrementalTask { |
大致流程为:
- 获取所有 ResourceSet 实例,其实也就是 App 所有的资源集合,包括自身以及依赖的。
- 加载每个 ResourceSet 下面的资源文件,检查并维护进内存中。
- 合并多个 ResourceSet 下面的资源信息。
下面一个个来进行分析。
获取所有 ResourceSet
MergeResources#doFullTaskAction
MergeResources#getConfiguredResourceSets
MergeResources#computeResourceSetList
private List<ResourceSet> getConfiguredResourceSets(ResourcePreprocessor preprocessor) { |
在分析代码前首先看看 ResourceSet 相关类图:
根据类图可以看到 DataSet 拥有一个 mSourceFile 字段,该字段表示的就是该 ResourceSet 所拥有的一系列文件。注意其是个 List ,一般条件下只有一个,但是可能会有多个,稍后第三步就将看到。
- 获取应用程序的 ResourceSet 列表,这里会获取到两个实例,其 sourceFile 各只有一个文件夹分别是 src/main/res 以及 src/debug/res。注:因为执行的是 assembleDebug,所以会有 src/debug/res。
- 获取应用程序依赖库的 ResourceSet 列表,这里的数量根据依赖而定,不过 sourceFile 一般也是一个,暂时不知道什么情况会出现多个。
- 给 mainResourceSet 添加以下两个目录,这两个目录分别是上述处理 RenderScript 以及 ResValues 生成的目录,添加完毕后该 ResourceSet 就会拥有三个 sourceFile。
- build/generated/res/rs/debug
- build/generated/resValues/debug
- 为每个 ResourceSet 创建对应的 GeneratedResourceSet ,然后设置给 ResourceSet。
- 将每个 GeneratedResourceSet 间隔插入到 ResourceSet 列表中,最终顺序为 A-Generated、A、B-Generated、B 。
看到这又产生疑问了,为什么需要这个 GeneratedResourceSet,这难道不是多此一举吗?
原因是在资源合并过程中会自动生成一些资源(比如后面会说到的 vector 资源的处理),这些资源就被放入对应的 GeneratedResourceSet 中,这又会引出两个问题。
- 为什么不直接放入 ResourceSet ?根据注释这是因为如果直接放入其中,如果增量编译失败那么又需要重新去获取 ResourceSet,影响效率,因为才会这么做。
- 为什么需要按这种顺序进行插入?因为最终生成资源时会倒序进行处理,这样能保证 GeneratedResourceSet 比起对应的 ResourceSet 优先级低,但是又比原先优先级低于它的 ResourceSet 优先级高。
加载所有 ResourceSet
MergeResources#doFullTaskAction
MergeResources#getConfiguredResourceSets
ResourcesSet#loadFromFiles
从上述类图可以看出,ResourceSet 没有重写 loadFromFile ,但是 GeneratedResourceSet 重写了。
// DataSet |
- 读取每个 sourceFile 文件夹。
- 检查该 ResourceSet 是否存在重复资源。
读取 sourceFile 文件夹
readSourceFolder 方法由 ResourceSet 进行实现。
protected void readSourceFolder(File sourceFolder, ILogger logger) |
- 根据文件夹名称解析文件夹类型以及限定信息(- 后面的),生成 FolderData 实例。(细节忽略)
- 对每个 sourceFile 下的每个子文件进行读取。
private void parseFolder(File sourceFolder, File folder, FolderData folderData, ...) { |
- 读取每个资源文件,并据此创建 ResourceFile 实例。
- 处理新生成的 ResourceFile 实例。
private ResourceFile createResourceFile(@NonNull File file, |
- 校验资源文件后缀名,比如 raw 文件夹,所有文件后缀都可,drawable、mipmap 文件夹,支持后缀为 xml、png、9.png、gif、jpeg、jpg、bmp、webp 文件。
- 只有 values 相关文件夹 folderData.type == null ,因此非 values 文件夹下的文件才会进入。
- 当 minSdk 小于 21、文件后缀为 xml 、位于 drawable 文件夹、同时根节点为 vector 时需要预处理。具体流程为在每个图片质量文件夹中增加一个对应的 png 文件以及在 anydpi 增加一个该 xml 文件。然后创建 ResourceFile 实例返回,该实例文件类型为 GENERATED_FILES 。
- 不需要预处理那么直接创建 ResourceFile 实例返回,该实例文件类型为 SINGLE_FILE 。
- 如果是 values 文件夹下的文件,需要对每个文件进行解析,然后创建 ResourceFile 实例返回,该实例文件类型为 XML_VALUES。
主要关注下 values 文件夹下的资源文件的解析。
List<ResourceItem> parseFile() throws MergingException { |
读取每一个节点,判断单个文件中是否存在重复的,如存在就抛出异常,不存在则存起来,对于 declare-styleable 会将其内部的 attr 节点(需要有 format 属性或者是 enum 或 flag)也加入到 resources 中,然后以此创建 ResourFile 实例。
回到上文 ,对每个资源文件处理生成 ResourceFile 实例后还需要调用 processNewResourceFile
进行处理。
private void processNewResourceFile(File sourceFolder, ResourceFile resourceFile) { |
- 如果该实例文件类型为 GENERATED_FILES 那么由其对应的 GeneratedResourceSet 进行处理。
- 如果该实例文件类型非 GENERATED_FILES 那么由当前 ResourceSet 进行处理。
处理过程就是将相关信息维护到以下三个数据结构中,注意 ListMultimap 与普通 Map 不同 put 时如果遇到相同 key 并不会进行覆盖,而是会在该 key 对应的列表中添加它。
- mSourceFileToDataFilesMap ListMultimap sourceFile => ResourceFile
- mDataFileMap HashMap file => ResourceFile
- mItems ListMultimap key(诸如 attr/cardViewStyle) => ResourceItem
检查 ResourceSet 资源
当处理完该 ResourceSet 下所有的文件时,就会调用 checkItems
来检查是否存在重复。
protected void checkItems() throws DuplicateDataException { |
item 的状态不用考虑,现在全是 touched,只有当增量编译的时候才会出现 removed,取出每个 key 对应的 ResourceItem 列表,如果发现列表数大于 1 个,那么后续就会抛出异常。
合并多个 ResourceSet 资源
当资源信息全部读取进内存后,需要进行资源合并,合并后的资源将做为 Apk 的资源。
public void mergeData(@NonNull MergeConsumer<I> consumer, boolean doCleanUp) |
遍历所有的 ResourceSet 实例,将其内部所有的 ResourceFile 实例(也就是该 App 所有的资源信息)全部加入到 dataItemKeys 中。
对于 declare-styleable 资源,需要对其的内部属性进行合并。
忽略 declare-styleable 下面的有 format 属性或者 enum、flag 的 attr 资源,因为第二步已经加了。
由于是逆序遍历,在多个 ResourceSet 中遇到重复资源了就只会写入优先级最高的那一个。
将最终选择资源传递给 consumer 也就是 MergedResourceWriter 进行输出。
合并 declare-styleable
上面 mergeData 每遍历到一个 declare-styleable 资源,都会编译所有 ResourceSet 的资源,收集相同名称的 declare-styleable 资源,然后调用 mergeItems。
protected void mergeItems(...) throws MergingException { |
原理大概是遍历所有相同名称的 declare-styleable 资源,收集所有内部的 attr,然后创建一个 MergedResourceItem ,然后传递给 MergedResourceWriter。
使用 MergedResourceWriter 输出
可以看到在 mergeData 方法中首先会调用 consumer.start ,然后中间会调用若干次 consumer.addItem,最终调用 consumer.end。跟进下这三个方法。
public void start(@NonNull DocumentBuilderFactory factory) throws ConsumerException { |
start 方法没什么特别的就是初始化一些成员属性。
public void addItem(@NonNull final ResourceItem item) throws ConsumerException { |
- 对于 values 资源,按照限定符进行分开存放。
- 如果文件类型为 GENERATED_FILES,那么需要自动生成对应的文件,最终通过 FileGenerationWorkAction 进行生成,不展开了。
- 对于非 values 资源,新建个 CompileResourceRequest 实例存放。
public void end() throws ConsumerException { |
- 处理 values 资源,以及等待 vector 生成对应的 png 图片生成完成。
- 编译资源,内部就是调用 aapt2 工具进行处理。
public void end() throws ConsumerException { |
- 在每个限定文件夹下生成 values.xml 。比如 values/values.xml、values-v23/values.xml 等等。
- 使用 aapt2 编译生成的每个 values.xml 文件。
至此该 Task 处理完毕,其重要内容就是把所有资源按优先级进行合并,将所有的 values 资源合并生成 values.xml 然后,然后使用 aapt2 进行编译。
createDebugCompatibleScreenManifests
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#createMergeApkManifestsTask
public void createMergeApkManifestsTask( |
具体看下 CompatibleScreensManifest.ConfigAction。
public class CompatibleScreensManifest extends DefaultAndroidTask { |
这个 Task 主要是配合 splits 分包的,由于一般情况下载 Google Play 发布时才可能用到,暂时先不关注。具体可以看下这里。
processDebugManifest
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#createMergeApkManifestsTask
protected AndroidTask<? extends ManifestProcessorTask> createMergeManifestTask(...) { |
很明显这个 Task 是用于合并清单文件的,代码逻辑很复杂暂时就先不看了,先根据 官方文档 了解下原理。
首先会将所有清单文件按优先级排序,然后将优先级最低的合并到优先级第二低的,然后再合并到第三低的,依次类推,直到合并到优先级最高的清单文件中。
清单文件优先级如下:
构建变体的清单文件,以 demoDebug(productFlavor = demo,buildType = debug)为例。
a. src/demoDebug/AndroidManifest.xml
b. src/demo/AndroidManifest.xml
c. src/debug/AndroidManifest.xml
应用程序的主清单文件
依赖库中的清单文件(先依赖的优先级高)
如果优先级较低的清单中的某个元素与优先级较高的清单中的所有元素都不匹配,则会将该元素添加到合并后的清单中。如果有匹配的元素,则合并工具会尝试将每个元素的所有属性组合到同一元素中。对于属性合并规则,官方文档又提供了下表来进行描述。
高优先级属性 | 低优先级属性 | 属性的合并结果 |
---|---|---|
没有值 | 没有值 | 没有值(使用默认值) |
值 B | 值 B | |
值 A | 没有值 | 值 A |
值 A | 值 A | |
值 B | 冲突错误 - 您必须添加合并规则标记 |
大部分属性合并都是按上表,不过以下情况有些特殊;
<manifest>
元素中的属性绝不会合并在一起,只会使用优先级最高的清单中的属性,也就是说会抛弃低优先级(例如库文件中的清单文件)的该元素属性。<uses-feature>
和<uses-library>
元素中的android:required
属性使用 OR 合并,也就是说只要有一个为 true,那么最终就是 true。- 绝不会在清单之间匹配
<intent-filter>
元素。每个该元素都被视为唯一的元素,并添加到合并后的清单中的共同父元素中。
总结下其实就是读取低优先级的清单文件的每个元素,在高优先级的清单文件中查找是否存在该元素,如果不存在那么就直接新增,如果存在那么进行属性合并,整个低优先级清单文件都合并到高优先级的清单文件中后,再与更高优先级的进行合并。
splitsDiscoveryTaskDebug
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createApkProcessResTask
TaskManager#createProcessResTask
public AndroidTask<ProcessAndroidResources> createProcessResTask(...) { |
这个 Task 也是 split 分包相关的,暂时忽略。
processDebugResources
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createApkProcessResTask
TaskManager#createProcessResTask
public AndroidTask<ProcessAndroidResources> createProcessResTask(...) { |
方法内部代码很复杂,大致做的内容就是将前面编译后的资源以及合并后的清单文件做为输入源使用 aapt2 工具进行链接生成 resources-debug.ap_ 以及 R.java 文件。
其实这就是 apk 中所有资源,将 ap_ 修改为 apk 就可以看到熟悉的 res 文件夹、resource.arsc 文件、AndroidManifest.xml 文件。
javaPreCompileDebug
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#addCompileTask
ApplicationTaskManager#createJavacTask
public AndroidTask<? extends JavaCompile> createJavacTask( |
具体看下 JavaPreCompileTask.ConfigAction 。
public class JavaPreCompileTask extends BaseTask { |
获取所有 AnnotationProcessor 类名,然后写入
build/intermediates/javaPrecompile/debug/annotationProcessors.json 中。
获取 AnnotationProcessor
JavaPreCompileTask#collectAnnotationProcessors
private static List<ResolvedArtifactResult> collectAnnotationProcessors( |
在所有的 classpath 中寻找文件 resources/META-INF.services/javax.annotation.processing.Processor
文件,该文件中会将该库所声明的所有注解处理器类全名列出来。如需学习注解处理器的基本使用,可以参考 这篇文章。
compileDebugJavaWithJavac
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#addCompileTask
ApplicationTaskManager#createJavacTask
public AndroidTask<? extends JavaCompile> createJavacTask(...) { |
具体看下 JavaCompileConfigAction。
public class JavaCompileConfigAction implements TaskConfigAction<AndroidJavaCompile> { |
设置待编译的源码集,对于 assembleDebug 而言包括:
- src/main/java
- src/debug/java
- xxx/build/generated/source/r/debug 稍后 aapt2 链接生成的 R.java 文件路径
- xxx/build/generated/source/buildConfig/debug 稍后自动生成的 BuildConfig.java 文件路径
- xxx/build/generated/source/aidl/debug 稍后编译 Aidl 文件生成的 java 文件路径。
- xxx/build/generated/source/rs/debug 稍后编译 RenderScript 文件生成的 java 文件路径。
将 android.jar(~/Library/Android/sdk/platforms/android-30/android.jar) 添加进入 Classpath 中。
注:对于 android.jar 其内部包含 Android 开放的所有 API,但是每个方法内部都是直接抛出 RuntimeException,最终其不会被打入 apk,因为 api 的具体实现早就在系统中了,其作用只是使编译通过罢了。
设置编译后字节码的输出路径 xxx/build/intermediates/classes/debug。
如果存在注解处理器那么添加 -processor 标志,如果注解处理器参数不为空那么添加 -A 标志,同时通过 -s 生成文件的目录。
配置阶段结束后,稍后将会执行 AndroidJavaCompile 这个 Task,而其继承于 Gradle 自带得 JavaCompile,内部就是编译代码。
compileDebugNdk
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#createNdkTasks
public void createNdkTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) { |
NdkCompile 这个 Task 主要工作为,借助与 ndk 路径下的 ndk-build 工具来编译构建 C 代码,具体的暂时不看,用到再说。
compileDebugSources
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#createAnchorTasks
TaskManager#createCompileAnchorTask
private void createCompileAnchorTask(...) { |
这个 Task 只是一个锚点,标志着处理 Shader 开始。
mergeDebugShaders
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createShaderTask
public void createShaderTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) { |
这个 Task 是用于处理 Vulkan 的,作用是合并以下两个目录文件到 xxx/build/intermediates/shaders/debug 。
- xxx/main/shaders
- xxx/debug/shaders
Vulkan 在 Android 7.0 以上支持,可以理解为是 OpenGL 的替代品,入门可以参考这篇文章,暂时用不到先忽略。
compileDebugShaders
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createShaderTask
public void createShaderTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) { |
这个 Task 紧接上一个,作用是将 xxx/build/intermediates/shaders/debug 文件夹下的 vert、tesc、tese、geom、frag、comp 编译成 spv 文件。
generateDebugAssets
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#createAnchorTasks
public void createAnchorTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) { |
这个 Task 只是一个锚点,标志着处理 Assets 开始。
mergeDebugAssets
ApplicationTaskManager#createTasksForVariantScope
TaskManager#createMergeAssetsTask
public AndroidTask<MergeSourceSetFolders> createMergeAssetsTask(...) { |
- 收集应用程序所有的 assets 目录,对于 assembleDebug 其包含以下两个目录。
- xxx/src/main/assets
- xxx/src/debug/assets
- 设置合并后的目录,对于 assembleDebug 其为 xxx/build/intermediates/assets/debug。
- 执行合并操作,其实和上面合并 Shaders 是一样的,就是直接复制过去。
extractTryWithResourcesSupportJarDebug
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#addCompileTask
ApplicationTaskManager#createPostCompilationTasks
TaskManager#maybeCreateDesugarTask
private void maybeCreateDesugarTask(...) { |
这个 Task 主要是将 libthrowable_extension.jar 拷贝到 xxx/build/intermediates/processing-tools/runtime-deps/debug/desugar_try_with_resources.jar ,该 jar 最终会被打包到 apk 中去。通过 jadx 反编译发现其内部只有一个 ThrowableExtension ,好像是用于支持 try-with-resources 语法,暂时不是很明确该类作用。
transformClassesWithStackFramesFixerForDebug
这是个 Transform Task,对应的实现类为 FixStackFramesTransform。注册时机为:
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#addCompileTask
ApplicationTaskManager#createPostCompilationTasks
TaskManager#maybeCreateDesugarTask
private void maybeCreateDesugarTask(...) { |
大致看了看 FixStackFramesTransform 的逻辑,内部会遍历所有依赖的 jar 包,然后使用 ASM 来重新计算每个类的栈帧信息。目的是当运行 Desugar 时,确保栈帧信息在类文件中是有效的,防止字节码在 JVM 1.7 及以上不合法。
猜想:目的是为了处理依赖库可能存在低版本的 Java 字节码??
transformClassesWithDesugarForDebug
这是个 Transform Task,对应的实现类为 DesugarTransform。注册时机为:
ApplicationTaskManager#createTasksForVariantScope
ApplicationTaskManager#addCompileTask
ApplicationTaskManager#createPostCompilationTasks
TaskManager#maybeCreateDesugarTask
private void maybeCreateDesugarTask(...) { |
首先将 desugar_deploy.jar (保存在 builder.jar 中,和 libthrowable_extension.jar 放在一起) 拷贝到临时目录,当虚拟机退出时会删除该文件,监听方法为:Runtime.getRuntime().addShutdownHook(thread: Thread)
,通过 jadx 反编译发现其内部包含有 ASM 代码。
DesugarTransform 内部处理文件都是异步执行的(借助于 WaitableExecutor),这样可以提高效率。
:app:transformClassesWithStackFramesFixerForDebug SKIPPED |