当前位置:首页 > 科技新闻 > 移动平台 > 正文

补齐Android技能树——从AGP构建过程到APK打包过程
2021-09-24 14:55:04

Android Gradle Plugin​,简称 AGP,老早之前就想好好研究下Android APK的打包过程,毕竟 ​APK包体积优化​ 的前置知识之一。

奈何当时的知识储备严重不足,硬啃着实难受,在学了两周的Gradle后 ,觉得应该有 一战之力 了,所以这一节来了!

补齐Android技能树——从AGP构建过程到APK打包过程_java

内容较多,建议先收藏,有时间再慢慢细品~

0x1、网上流传的三张APK打包流程图

​Android官网​​ 有一张新的打包流程图(左),相比起旧的流程图(右)更抽象,隐藏了很多细节:

补齐Android技能树——从AGP构建过程到APK打包过程_java_02

在 ​​Android Studio Project Site​​ 还找到了一张更详细的图:

补齐Android技能树——从AGP构建过程到APK打包过程_android_03

如果只是满足于一个写基础业务的Android开发仔,了解下足矣,不过如果想有更深的造诣,还是建议往下学的。

比如面试时(我自己脑补的~):

  • 面试官:简历上写的做过APK体积优化?说说都做了哪方面的优化:
  • 你:从资源大头入手,把png图片都转成webp,使用AndResGuard对资源进行混淆;
  • 面试官:用Gradle写一个webp转换插件,说下思路;
  • 你:不会,我一般右键直接转换…
  • 面试官:那说下AndResGuard的大概原理;
  • 你:

补齐Android技能树——从AGP构建过程到APK打包过程_android_04

本节就来了解下AGP的构建过程,以及简单了解下APK的打包过程~

Tips:Tasks那么多,不可能一个个去精读源码解析,不同插件版本还有差异,不如授之以渔,本文的目的就是让读者遇到问题时懂得如何追根溯源,找到对应的源码。



0x2、如何查看插件源码

研究对象是AGP的源码,所以要先搞一份源码,方法有下述几种:

1. 下载完整源码

如果磁盘空间比较充足,可以通过repo的方式,将Android Gradle Plugin的源码下载到本地(貌似30多G):

# 最新源码的只有3.4.0的
repo init -u https://android.googlesource.com/platform/manifest -b gradle_3.4.0
repo sync

2. 下载部分源码

当然不需要编译的话,可以直接下对应源码包,来到下述地址:​​build-system​

点tgz下载:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_05

然后用VS Code之类的代码查看工具查看即可~

3. 取巧(推荐)

在app层级的build.gradle添加下述依赖:

implementation 'com.android.tools.build:gradle:3.4.0'

build下,然后在左侧 ​External Libraries​ 即可找到源码:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_06



0x3、阅读源码前的一些补充

阅读源码前建议温习下我前面写的三篇文章,另外补充点姿势:

Gradle Plugin 中的Task主要有三种:​普通Task​、​增量Task​、​Transform​。

Task一般会继承 ​DefaultTask​ 或 ​IncrementalTask​,而 ​@TaskAction​ 注解的方法,就是此Task做的事。

继承 ​IncrementalTask​ 的类为增量Task,这个增量是相对于全量来说的,全量指的是:调用完clean后第一次编译过程,修改代码或资源后再次编译,就是增量编译。几个关键方法:

public abstract class IncrementalTask extends BaseTask {
// 是否需要增量,默认false
@Internal protected boolean isIncremental() { }

// 需要子类实现,全量时执行的任务
protected abstract void doFullTaskAction() throws Exception;

// 增量时执行的任务,默认什么都不执行,参数是增量时修改过的文件
protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws Exception{ }

@TaskAction
void taskAction(IncrementalTaskInputs inputs) throws Exception {
// 判断是否是增量,是执行doIncrementalTaskAction,否则执行doFullTaskAction


// 获取修改文件
private Map<File, FileStatus> getChangedInputs(IncrementalTaskInputs inputs) { }
}

至于 ​Transform​(变换),是Android官方提供给开发者,在**.class → .dex转换期间用来修改.class文件的一套API**,留意 ​transform()​ 方法的实现就好。



0x4、执行gradle assemble的Task链

我们常常使用下面的命令来打包APK:

gradlew assemble

可以由此入手,看下打包一次都涉及到了哪些Task,键入下述命令(linux、mac使用./gradlew):

gradlew assemble --console=plain

输出结果及要点简述如下所示:

:app:preBuild UP-TO-DATE    → 空task,锚点
:app:preDebugBuild → 空task,锚点
:app:compileDebugAidl NO-SOURCE → 处理AIDL
:app:checkDebugManifest → 检查Manifest是否存在
:app:compileDebugRenderscript NO-SOURCE → 处理renderscript
:app:generateDebugBuildConfig → 生成 BuildConfig.java
:app:mainApkListPersistenceDebug → 生成 app-list.gson
:app:generateDebugResValues → 生成resvalue,generated.xml
:app:generateDebugResources → 空task,锚点
:app:mergeDebugResources → 合并资源文件
:app:createDebugCompatibleScreenManifests → manifest文件中生成compatible-screens,指定屏幕适配
:app:processDebugManifest → 合并manifest.xml文件
:app:processDebugResources → aapt打包资源
:app:compileDebugKotlin → 编译Kotlin文件
:app:prepareLintJar UP-TO-DATE → 拷贝 lint jar包到指定位置
:app:generateDebugSources → 空task,锚点
:app:javaPreCompileDebug → 生成 annotationProcessors.json 文件
:app:compileDebugJavaWithJavac → 编译 java文件
:app:compileDebugNdk → 编译ndk
:app:compileDebugSources → 空task,锚点
:app:mergeDebugShaders → 合并 shader文件
:app:compileDebugShaders → 编译 shaders
:app:generateDebugAssets → 空task,锚点
:app:mergeDebugAssets → 合并 assests文件
:app:validateSigningDebug → 验证签名
:app:signingConfigWriterDebug → 编写SigningConfig信息
:app:checkDebugDuplicateClasses → 检查重复class
:app:transformClassesWithDexBuilderForDebug → class打包成dex
:app:transformDexArchiveWithExternalLibsDexMergerForDebug → 打包第三方库的dex
:app:transformDexArchiveWithDexMergerForDebug → 打包最终的dex
:app:mergeDebugJniLibFolders → 合并jni lib 文件
:app:transformNativeLibsWithMergeJniLibsForDebug → 合并jnilibs
:app:transformNativeLibsWithStripDebugSymbolForDebug → 去掉native lib里的debug符号
:app:processDebugJavaRes NO-SOURCE → 处理java res
:app:transformResourcesWithMergeJavaResForDebug → 合并java res
:app:packageDebug → 打包apk
:app:assembleDebug → 空task,锚点
:app:extractProguardFiles → 生成混淆文件

# 还会打一个release包,task和上述基本一致,此处省略~

当然,也可以直接在 ​Build​ 窗口直接查看,双击右侧Gradle窗口中assemble的Task,然后观察此窗口:

补齐Android技能树——从AGP构建过程到APK打包过程_jvm_07

啧啧,还可以看到每个Task的执行时间,不错,但先不跟每个Task具体内容,而是跟下AGP的构建过程~

0x5、AGP的构建过程

上一节将Gradle插件时说过,每个插件都会配置一个 ​id名字.properties​ 的文件,在此写上插件的实现类,全局搜定位到下述文件:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_08

打开:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_09

指向:​AppPlugin​ 类,跟下:

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_10

上节说过:插件类都继承于 ​Plugin​,入口函数 ​apply()​,但在这里没找到,跟下:​AbstractAppPlugin​ → ​BasePlugin​。

① BasePlugin

补齐Android技能树——从AGP构建过程到APK打包过程_android_11

行吧,在BasePlugin中重写了 ​apply()​ 方法,里面调用了两个函数,先跟下:​basePluginApply()补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_12

执行一些检查操作,接着是 插件的初始化及配置,而另一个函数:​pluginSpecificApply()​ 则是空实现,接着跟下:配置项目、配置扩展及创建Tasks的过程。

② configureProject() → 配置项目

补齐Android技能树——从AGP构建过程到APK打包过程_android_13

创建DataBindingBuilder实例,强制使用不低于当前所支持的最小插件版本,应用Java插件,如果启用了构建缓存选项,创建buildCache实例,添加了一个回调:所有project执行完后执行资源回收相关操作。

③ configureExtension() → 配置DSL扩展

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_14

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_15

补齐Android技能树——从AGP构建过程到APK打包过程_android_16

完成下述几项工作:

  • ① 创建build.gradle中的Android DSL;
  • ② 创建VariantFactory、TaskManager、VariantManager实例;
  • ③ 注册新增/移除配置的回调,包括:signingConfig,buildType,productFlavor;
  • ④ 创建默认的debug签名、debug和release两种buildType;

④ createTasks() → 创建Tasks

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_17

跟下 ​createAndroidTask()​:

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_18

跟下 ​createAndroidTasks()​:

补齐Android技能树——从AGP构建过程到APK打包过程_java_19

注意下:这里遍历了所有的variantScope,然后调用 ​createTasksForVariantData()​ 创建变体数据对应的Tasks:

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_20

跟下:​createTasksForVariantScope()​:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_21

抽象方法,看下哪里实现了这个方法,搜下:​extends TaskManager

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_22

最终定位到了:​ApplicationTaskManager​ 类

补齐Android技能树——从AGP构建过程到APK打包过程_android_23

噢吼,就是在这里完成APK打包过程的Tasks,可以简单跟跟验证下:​createAnchorTasks()​,创建锚点Tasks:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_24

跟下:​createVariantPreBuildTask()

补齐Android技能树——从AGP构建过程到APK打包过程_java_25

2333,跟上面的APK打包Task链的相呼应,AGP插件的构建过程就跟到这里,接着了解下APK打包的Task。

0x6、Apk的打包过程

Tips:分享下搜索Task的实现类的技巧 → 全局搜 “xxx”, “yyy” 即可快速定位对应Task类,如 “compile”, “Aidl”,或者搜索整个Task,然后删删删匹配。

1. compileDebugAidl

过程简述:​将.aidl文件通过aidl工具转换成编译器能够处理的Java接口文件​ 相关代码:AidlCompile.java → AidlProcessor.java → call()



2. checkDebugManifest

过程简述:​检查AndroidManifest.xml文件是否存在​ 相关代码:CheckManifest.java



3. compileDebugRenderscript

过程简述:​处理Renderscript文件(.rs)​ 相关代码:RenderscriptCompile.java



4. generateDebugBuildConfig

过程简述:​生成 BuildConfig.java 文件​ 相关代码:GenerateBuildConfig.java



5. mainApkListPersistenceDebug

过程简述:​持久化APK数据到apk-list.gson中​ 相关代码:MainApkListPersistence.kt



6. generateDebugResValues

过程简述:​遍历res下的values目录下xml文件,生成resValues文件generated.xml​ 相关代码:GenerateResValues.java → generate() → ResValueGenerator.java



7. mergeDebugResources

过程简述:​使用AAPT2合并资源文件​ 相关代码:MergeResources.doFullTaskAction() → ResourceMerger.mergeData() → MergedResourceWriter.end() → mResourceCompiler.submitCompile() → AaptV2CommandBuilder.makeCompileCommand()

核心源码解析:

补齐Android技能树——从AGP构建过程到APK打包过程_java_26

实现了isIncremental()方法,返回true,说明支持增量编译,跟下全量编译方法 ​doFullTaskAction()

ResourcePreprocessor preprocessor = getPreprocessor();
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor)

补齐Android技能树——从AGP构建过程到APK打包过程_jvm_27

接着往下走:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_28

继续:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_29

点进merger.mergeData() → ResourceMerger.mergeData() → DataMerger.mergeData()

补齐Android技能树——从AGP构建过程到APK打包过程_jvm_30

呕吼,实际上调用的还是 ​MergedResourceWriter​ 类里的方法,跟下addItem():

补齐Android技能树——从AGP构建过程到APK打包过程_android_31

不同文件会创建对应的 ​CompileResourceRequest​ 实例,并添加到 ​mCompileResourceRequests​ 中,后者是一个ConcurrentLinkedQueue队列,资源最后会在end()方法处处理:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_32

最终调用 ​AaptV2CommandBuilder.makeCompileCommand()​ 方法生成aapt2命令去处理资源。

Tips:将图片转为webp格式的插件一般在此Task前处理~



8. createDebugCompatibleScreenManifests

过程简述:​manifest文件中生成compatible-screens,用于屏幕适配​ 相关代码:CompatibleScreensManifest.kt



9. processDebugManifest

过程简述:​合并AndroidManifest.xml文件​ 相关代码:ProcessApplicationManifest.java、ProcessLibraryManifest.java



10. processDebugResources

过程简述:​调用aapt2 link 打包资源并生成R.java文件​ 相关代码:TaskManager.java → createProcessResTask()



11. compileDebugKotlin

过程简述:​编译Kotlin文件为字节码​ 相关代码:没找到…可能在kotlin插件源码里



12. prepareLintJar

过程简述:​拷贝lint jar包到指定位置​ 相关代码:PrepareLintJar.java



13. avaPreCompileDebug

过程简述:​生成annotationProcessors.json文件​ 相关代码:JavaPreCompileTask.java



14. ompileDebugJavaWithJavac

过程简述:​编译java文件​ 相关代码:AndroidJavaCompile.java



15. compileDebugNdk

过程简述:​编译NDK​ 相关代码:NdkCompile.java



15. mergeDebugShaders

过程简述:​合并Renderscript文件(.rs)​ 相关代码:MergeSourceSetFolders.java



16. compileDebugShaders

过程简述:​编译Renderscript文件(.rs)​ 相关代码:ShaderCompile.java



17. mergeDebugAssets

过程简述:​合并assets文件​ 相关代码:MergeSourceSetFolders.java



18. validateSigningDebug

过程简述:​验证签名​ 相关代码:ValidateSigningTask.kt 附加信息:检查当前Variant的签名配置中是否存在密钥库文件,如果当前密钥库默认为debug keystore,那密钥库不存在也会进行相应的创建。



19. signingConfigWriterDebug

过程简述:​编写SigningConfig信息​ 相关代码:SigningConfigWriterTask.kt



20. checkDebugDuplicateClasses

过程简述:​检查重复class​ 相关代码:CheckDuplicateClassesTask.kt 附加信息:检查项目外部依赖是否不包含重复类,打包成dex的时候再检测报错不怎么友好,所以引入了这个Task用于快速失败。



21. transformClassesWithDexBuilderForDebug

过程简述:​将class打包成dex​ 相关代码:DexArchiveBuilderTransform.java

核心代码解析:

定位到 ​transform()​ 方法,可以看到对class的处理分为了两种,目录下的 class和.jar里的class:

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_33

跟下 ​processJarInput()​:

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_34

继续跟:​convertJarToDexArchive()

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_35

补齐Android技能树——从AGP构建过程到APK打包过程_java_36

对class两种处理方式,最后都走到 ​convertToDexArchive()​,其中调用了 ​launchProcessing()​:

补齐Android技能树——从AGP构建过程到APK打包过程_jvm_37

补齐Android技能树——从AGP构建过程到APK打包过程_java_38

补齐Android技能树——从AGP构建过程到APK打包过程_android_39

这里的 ​dexArchiveBuilder.convert()​ 其实就是内部调用dx或d8来打dex,跟下赋值处:

补齐Android技能树——从AGP构建过程到APK打包过程_android_40



22. transformDexArchiveWithExternalLibsDexMergerForDebug

过程简述:​打包第三方库的dex​ 相关代码:ExternalLibsMergerTransform.kt 核心代码解析:

同样跟 ​transform()​:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_41

创建了一个 ​DexMergerTransformCallable​ 实例,然后调 ​​call()​​ 方法:

补齐Android技能树——从AGP构建过程到APK打包过程_jvm_42

比较简单,就是调下dx或d8将上面生成的依赖库的dex合并成一个dex。



23. transformDexArchiveWithDexMergerForDebug

过程简述:​打包最终的dex​ 相关代码:DexMergerTransform.transform() → mergeDex() 核心代码解析:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_43

跟下 ​submitForMerging()​ :

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_44

也是创建了一个 ​DexMergerTransformCallable​ 实例,剩余逻辑同上~



24. mergeDebugJniLibFolders

过程简述:​合并jni lib文件​ 相关代码:MergeSourceSetFolders.java



25. transformNativeLibsWithMergeJniLibsForDebug

过程简述:​合并jnilibs​ 相关代码:MergeJavaResourcesTransform.java



26. transformNativeLibsWithStripDebugSymbolForDebug

过程简述:​去掉native lib里的debug符号​ 相关代码:StripDebugSymbolTransform.java



27. processDebugJavaRes

过程简述:​处理java res​ 相关代码:MergeJavaResourcesTransform.java



28. transformResourcesWithMergeJavaResForDebug

过程简述:​合并java res​ 相关代码:MergeJavaResourcesTransform.java



29. packageDebug

过程简述:​打包APK​ 相关代码:PackageApplication.java → PackageAndroidArtifact.doTask()

核心代码如下:

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_45

而上面的这些updateXxx()方法,调用的都是:​IncrementalPackager → updateFiles()

补齐Android技能树——从AGP构建过程到APK打包过程_java_46

补齐Android技能树——从AGP构建过程到APK打包过程_gradle_47

最终调用mApkCreator.writeZip将上述内容写入到APK中。



30. extractProguardFiles

过程简述:​生成混淆文件​ 相关代码:ExtractProguardFiles.java



补充:锚点Task → 空Task

上面的Tasks过滤了锚点Task,啥事锚点Task?答:空Task,用来表明处于某种状态

以 ​preBuild​ 为例,全局搜它,定位到: ​TaskManager → MAIN_PREBUILD​:

补齐Android技能树——从AGP构建过程到APK打包过程_移动开发_48

跟下引用处:​createTasksBeforeEvaluate()​:

补齐Android技能树——从AGP构建过程到APK打包过程_java_49

注册了一个名为 **​​MAIN_PREBUILD​​**的Task,但没有传闭包(任务内容),即空Task。



小结

以上就是本节的全部内容,看完好像懂了些什么,又说不出来懂了什么,没关系,毕竟有点偏理论,为后面Gradle的更深入学习及应用做铺垫而已,不急,有疑问或文中有误地方欢迎评论区指出,谢谢~

对了,喂,三点几了:

补齐Android技能树——从AGP构建过程到APK打包过程_java_50

本文摘自 :https://blog.51cto.com/u