Android打包流程Gradle Plugin 主要 Task 分析
一、Android 打包流程
官方流程图:
1.编译器将您的源代码转换成 DEX(Dalvik Executable) 文件(其中包括 Android 设备上运行的字节码),将所有其他内容转换成已编译资源。
2.APK 打包器将 DEX 文件和已编译资源合并成单个 APK。 不过,必须先签署 APK,才能将应用安装并部署到 Android 设备上。
3.APK 打包器使用调试或发布密钥库签署您的 APK:
4.在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,减少其在设备上运行时占用的内存。
以 Task 的维度来看 apk 的打包,则分为以下流程:
```
:android-gradle-plugin-source:preBuild UP-TO-DATE
:android-gradle-plugin-source:preDebugBuild
:android-gradle-plugin-source:compileDebugAidl
:android-gradle-plugin-source:compileDebugRenderscript
:android-gradle-plugin-source:checkDebugManifest
:android-gradle-plugin-source:generateDebugBuildConfig
:android-gradle-plugin-source:prepareLintJar UP-TO-DATE
:android-gradle-plugin-source:generateDebugResValues
:android-gradle-plugin-source:generateDebugResources
:android-gradle-plugin-source:mergeDebugResources
:android-gradle-plugin-source:createDebugCompatibleScreenManifests
:android-gradle-plugin-source:processDebugManifest
:android-gradle-plugin-source:splitsDiscoveryTaskDebug
:android-gradle-plugin-source:processDebugResources
:android-gradle-plugin-source:generateDebugSources
:android-gradle-plugin-source:javaPreCompileDebug
:android-gradle-plugin-source:compileDebugJavaWithJavac
:android-gradle-plugin-source:compileDebugNdk NO-SOURCE
:android-gradle-plugin-source:compileDebugSources
:android-gradle-plugin-source:mergeDebugShaders
:android-gradle-plugin-source:compileDebugShaders
:android-gradle-plugin-source:generateDebugAssets
:android-gradle-plugin-source:mergeDebugAssets
:android-gradle-plugin-source:transformClassesWithDexBuilderForDebug
:android-gradle-plugin-source:transformDexArchiveWithExternalLibsDexMergerForDebug
:android-gradle-plugin-source:transformDexArchiveWithDexMergerForDebug
:android-gradle-plugin-source:transformClassesWithDexBuilderForDebug
:android-gradle-plugin-source:transformDexArchiveWithExternalLibsDexMergerForDebug
:android-gradle-plugin-source:transformDexArchiveWithDexMergerForDebug
:android-gradle-plugin-source:mergeDebugJniLibFolders
:android-gradle-plugin-source:transformNativeLibsWithMergeJniLibsForDebug
:android-gradle-plugin-source:transformNativeLibsWithStripDebugSymbolForDebug
:android-gradle-plugin-source:processDebugJavaRes NO-SOURCE
:android-gradle-plugin-source:transformResourcesWithMergeJavaResForDebug
:android-gradle-plugin-source:validateSigningDebug
:android-gradle-plugin-source:packageDebug
:android-gradle-plugin-source:assembleDebug
```
这些Task的对应实现类和作用如下表所示:
二、Task简单介绍
我们的所有Gradle的构建工作都是由Task组合完成的,它可以帮助我们处理很多工作。每次构建(build)至少由一个project构成,一个project由一个至多个task构成。每个task代表了构建过程当中的一个原子性操作,比如编译、打包、发布等等这些操作。在Gradle环境下可以通过命令./gradlew tasks 查看当前工程所有的task。
Gradle 为我们提供了很多默认的task来进行使用,下面是Gradle 官网文档上对copy Task 的使用提供的例子。除了copy,还有delete、upload、zip等许多使用的task。这些基础task可以用来解决插件资源复制等问题。
官方网站:https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#N18D13
官网讲的很详细,我这边简要介绍一下。在 gradle plugin 中的 Task 主要有三种,一种是普通的 task,一种是增量 task,一种是 transform。
1. Task的创建和配置
Task创建主要是两种方式:直接使用task函数创建或者通过task容器TaskContainer对象的create()方法进行创建。
```
task hello{
}
this.task.create('hello'){
}
```
Task 配置:上面两种创建方式其实都有对应的重载方法,可以传入具体的配置参数来进行Task初始化配置。
2.Task的执行
当执行一个Task的时候,其实就是执行其拥有的actions列表,这个列表保存在Task对象实例中的actions成员变量中,其类型就是一个List。Task本质上又是由一组被顺序执行的Action对象构成,Action其实是一段代码块,类似与Java中的方法。这里主要介绍Task创建Action的两个方法,doFirst与doLast。doFirst{} 可以使代码在Gradle 的执行阶段中Task之前执行,而doLast{}则恰恰相反,是在Task之后执行。
3.Task的执行顺序
a.dependsOn 是task 配置参数之一,主要作用就是为task 添加依赖task ,保证task 之间的执行顺序。
A-->B-->C
b.TaskInputs TaskOutputs
TaskInputs:
Task的输入类,参数可以接收为任意对象以及文件、文件夹。
TaskOutputs:
TaskOutputs files ( );
TaskOutputs file ( );
TaskOutputs dir ( );
Task的输出类,只接收文件类型。
c.API指定执行顺序
Task的shouldRunAfter 方法和mustRunAfter方法可以控制一个Task应该或者一定在某个Task之后执行。通过这种方式可以在某些情况下控制任务的执行顺序,而不是通过强依赖的方式。
taskB.shouldRunAfter(taskA)表示taskB应该在taskA执行之后执行,这里并不是强制的,所以有可能任务顺序并不会按预设的执行。
taskB.mustRunAfter(taskA)表示taskB必须在taskA执行之后执行,执行任务的顺序是确认的。
4.挂接自定义Task到构建过程中
Gradle 的生命周期:
A. 初始化阶段(Initialization)
初始化阶段gradle会去解析项目根工程中setting.gradle中的include信息,确定哪些工程加入构建。
B. 配置阶段(Configuration)
配置阶段将解析所有工程的build.gradle脚本,配置project对象,创建、配置task等相关信息。
C. 执行阶段(Execution)
根据具体的gradle命令,执行对应相关的task以及其依赖的task。
而实际项目中我们将要挂接的正是gradle的执行阶段,因为只有在配置阶段完成,执行阶段gradle才会去执行系统默认task 以及自定义task 。project为我们提供了这样的方法project.afterEvaluate{}。
project.afterEvaluate{} 在配置完成后,可以保证获取到所有的task,包括系统默认执行的task,这样就可以将我们自定义的task 通过顺序执行指定,挂接到构建过程中。
后面我们有个例子详细解读。
5.Task的启用与禁用
Task中有个enabled属性,用于启用和禁用任务,默认是true,表示启用,设置为false则禁止该任务,在执行阶段该任务会被跳过。在实际项目中是很使用的一个技巧,如提升build编译速度,禁止一些测试相关的Task,从而缩短执行时间。
hello.enabled=false
三、重点Task分析
官方网站:https://android.googlesource.com/platform/tools/base/+/refs/tags/gradle_3.0.0/build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/GenerateBuildConfig.java
我们把这几个重点Task拿出来分析,因为这几个代表了 gradle 自动生成代码,资源的处理,以及 dex 的处理,算是 apk 打包过程中比较重要的几环。
generateDebugBuildConfig ----生成BuildConfig.java
processDebugManifest ----合并Manifest文件
mergeDebugResources ----合并资源文件
processDebugResources ---aapt打包资源
transformClassesWithDexBuilderForDebug ----class打包dex
transformDexArchiveWithExternalLibsDexMergerForDebug ----打包三方库的dex
transformDexArchiveWithDexMergerForDebug ----打包最终的dex
3.1 generateDebugBuildConfig
3.1.1 实现类
GenerateBuildConfig
3.1.2 代码调用链路
GenerateBuildConfig.generate -> BuildConfigGenerator.generate -> JavaWriter
3.1.3 主要代码分析
在 GenerateBuildConfig 中,主要生成代码的步骤如下:
生成 BuildConfigGenerator
添加默认的属性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME
添加自定义属性
调用 JavaWriter 生成 BuildConfig.java 文件
3.2 mergeDebugResources
3.2.1 实现类
MergeResources
3.2.2 调用链路
MergeResources.doFullTaskAction -> ResourceMerger.mergeData -> MergedResourceWriter.end -> QueueableAapt2.compile -> Aapt2QueuedResourceProcessor.compile -> AaptProcess.compile -> AaptV2CommandBuilder.makeCompile
3.2.3 代码分析
MergeResources 这个类,继承自 IncrementalTask,按照前面说的阅读增量 Task 代码的步骤,依次看三个方法的实现:isIncremental,doFullTaskAction,doIncrementalTaskAction
isIncremental
// 说明 Task 支持增量
protected boolean isIncremental() {
return true;
}
doFullTaskAction
通过 getConfiguredResourceSets() 获取 resourceSets,包括了自己的 res/ 和 依赖库的 res/ 以及 build/generated/res/rs
// MergeResources.doFullTaskAction()
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
创建 ResourceMerger
// MergeResources.doFullTaskAction()
ResourceMerger merger = new ResourceMerger(minSdk);
创建 QueueableResourceCompiler,因为 gradle3.x 以后支持了 aapt2,所以这里有两种选择 aapt 和 aapt2。其中 aapt2 有三种模式,OutOfProcessAaptV2,AaptV2Jni,QueueableAapt2,这里默认创建了 QueueableAapt2,resourceCompiler =QueueableAapt2
3.3 processDebugResources
3.3.1 实现类
ProcessAndroidResources
3.3.2 调用链路
ProcessAndroidResources.doFullTaskAction -> ProcessAndroidResources.invokeAaptForSplit -> AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink
3.3.3 代码分析
ProcessAndroidResources 也是继承自 IncrementalTask,但是没有重写 isIncremental,所以不是增量的 Task,直接看 doFullTaskAction 即可
doFullTaskAction
这个里面代码虽然多,但是主要的逻辑比较简单,就是调用 aapt2 link 去生成资源包。
这里会处理 splits apk 相关的内容,关于 splits apk 具体可以查看 splits apk,简单来说,就是可以按照屏幕分辨率,abis 来生成不同的 apk,从而让特定用户的安装包变小。
3.4 processDebugManifest
3.4.1 实现类
MergeManifests
3.4.2 调用链路
MergeManifests.dofFullTaskAction -> AndroidBuilder.mergeManifestsForApplication -> Invoker.merge -> ManifestMerge2.merge
3.4.3 代码分析
MergeManifests 也是继承了 IncrementalTask,但是没有实现 isIncremental,所以只看其 doFullTaskAction 即可。
这个 task 功能主要是合并 mainfest,包括 module 和 flavor 里的,整个过程通过 MergingReport,ManifestMerger2 和 XmlDocument 进行。
这里直接看 ManifestMerger2.merge() 的 merge 过程 。 主要有几个步骤:
获取依赖库的 manifest 信息,用 LoadedManifestInfo 标识
获取主 module 的 manifest 信息,替换主 module 的 Manifest 中定义的某些属性,替换成 gradle 中定义的属性 例如: package, version_code, version_name, min_sdk_versin 等等
3.5 transformClassesWithDexBuilderForDebug
3.5.1 实现类
DexArchiveBuilderTransform
3.5.2 调用链路
DexArchiveBuilderTransform.transform -> DexArchiveBuilderTransform.convertJarToDexArchive -> DexArchiveBuilderTransform.convertToDexArchive -> DexArchiveBuilderTransform.launchProcessing -> DxDexArchiveBuilder.convert
3.5.4 主要代码分析
在 DexArchiveBuilderTransform 中,对 class 的处理分为两种方式,一种是对 目录下的 class 进行处理,一种是对 .jar 里的 class 进行处理。
为什么要分为这两种方式呢?.jar 中的 class 一般来说都是依赖库,基本上不会改变,gradle 在这里做了一个缓存,但是两种方式最终都会调用到 convertToDexArchive,可以说是殊途同归吧。
convertJarToDexArchive 处理 jar
处理 .jar 时,会对 jar 包中的每一个 class 都单独打成一个 .dex 文件,之后还是放在 .jar 包中
四、插件化框架Task Hook
举例:滴滴插件化框架学习笔记之virtualapk-gradle-plugin
https://juejin.im/post/6847902216590721038