Android Gradle Plugin
,简称 AGP,老早以前就想好好研究下Android APK的打包过程,毕竟 APK包体积优化
的前置知识之一。java
奈何当时的知识储备严重不足,硬啃着实难受,在学了两周的Gradle后 ,以为应该有 一战之力 了,因此这一节来了!linux
内容较多,建议先收藏,有时间再慢慢细品~android
Android官网 有一张新的打包流程图(左),相比起旧的流程图(右)更抽象,隐藏了不少细节:web
在 Android Studio Project Site 还找到了一张更详细的图:面试
若是只是知足于一个写基础业务的Android开发仔,了解下足矣,不过若是想有更深的造诣,仍是建议往下学的。shell
好比面试时(我本身脑补的~):json
本节就来了解下AGP的构建过程,以及简单了解下APK的打包过程~缓存
Tips:Tasks那么多,不可能一个个去精读源码解析,不一样插件版本还有差别,不如授之以渔,本文的目的就是让读者遇到问题时懂得如何追根溯源,找到对应的源码。bash
研究对象是AGP的源码,因此要先搞一份源码,方法有下述几种:markdown
若是磁盘空间比较充足,能够经过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
复制代码
固然不须要编译的话,能够直接下对应源码包,来到下述地址:build-system
点tgz下载:
而后用VS Code之类的代码查看工具查看便可~
在app层级的build.gradle添加下述依赖:
implementation 'com.android.tools.build:gradle:3.4.0'
复制代码
build下,而后在左侧 External Libraries
便可找到源码:
阅读源码前建议温习下我前面写的三篇文章,另外补充点姿式:
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()
方法的实现就好。
咱们经常使用下面的命令来打包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,而后观察此窗口:
啧啧,还能够看到每一个Task的执行时间,不错,但先不跟每一个Task具体内容,而是跟下AGP的构建过程~
上一节将Gradle插件时说过,每一个插件都会配置一个 id名字.properties
的文件,在此写上插件的实现类,全局搜定位到下述文件:
打开:
指向:AppPlugin
类,跟下:
上节说过:插件类都继承于 Plugin
,入口函数 apply()
,但在这里没找到,跟下:AbstractAppPlugin
→ BasePlugin
。
行吧,在BasePlugin中重写了 apply()
方法,里面调用了两个函数,先跟下:basePluginApply()
执行一些检查操做,接着是 插件的初始化及配置,而另外一个函数:pluginSpecificApply()
则是空实现,接着跟下:配置项目、配置扩展及建立Tasks的过程。
建立DataBindingBuilder实例,强制使用不低于当前所支持的最小插件版本,应用Java插件,若是启用了构建缓存选项,建立buildCache实例,添加了一个回调:全部project执行完后执行资源回收相关操做。
完成下述几项工做:
跟下 createAndroidTask()
:
跟下 createAndroidTasks()
:
注意下:这里遍历了全部的variantScope,而后调用 createTasksForVariantData()
建立变体数据对应的Tasks:
跟下:createTasksForVariantScope()
:
抽象方法,看下哪里实现了这个方法,搜下:extends TaskManager
最终定位到了:ApplicationTaskManager
类
噢吼,就是在这里完成APK打包过程的Tasks,能够简单跟跟验证下:createAnchorTasks()
,建立锚点Tasks:
跟下:createVariantPreBuildTask()
2333,跟上面的APK打包Task链的相呼应,AGP插件的构建过程就跟到这里,接着了解下APK打包的Task。
Tips:分享下搜索Task的实现类的技巧 → 全局搜 "xxx", "yyy" 便可快速定位对应Task类,如 "compile", "Aidl",或者搜索整个Task,而后删删删匹配。
过程简述:将.aidl文件经过aidl工具转换成编译器可以处理的Java接口文件
相关代码:AidlCompile.java → AidlProcessor.java → call()
过程简述:检查AndroidManifest.xml文件是否存在
相关代码:CheckManifest.java
过程简述:处理Renderscript文件(.rs)
相关代码:RenderscriptCompile.java
过程简述:生成 BuildConfig.java 文件
相关代码:GenerateBuildConfig.java
过程简述:持久化APK数据到apk-list.gson中
相关代码:MainApkListPersistence.kt
过程简述:遍历res下的values目录下xml文件,生成resValues文件generated.xml
相关代码:GenerateResValues.java → generate() → ResValueGenerator.java
过程简述:使用AAPT2合并资源文件
相关代码:MergeResources.doFullTaskAction() → ResourceMerger.mergeData() → MergedResourceWriter.end() → mResourceCompiler.submitCompile() → AaptV2CommandBuilder.makeCompileCommand()
核心源码解析:
实现了isIncremental()方法,返回true,说明支持增量编译,跟下全量编译方法 doFullTaskAction()
ResourcePreprocessor preprocessor = getPreprocessor();
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor)
复制代码
接着往下走:
继续:
点进merger.mergeData() → ResourceMerger.mergeData() → DataMerger.mergeData()
呕吼,实际上调用的仍是 MergedResourceWriter
类里的方法,跟下addItem():
不一样文件会建立对应的 CompileResourceRequest
实例,并添加到 mCompileResourceRequests
中,后者是一个ConcurrentLinkedQueue队列,资源最后会在end()方法到处理:
最终调用 AaptV2CommandBuilder.makeCompileCommand()
方法生成aapt2命令去处理资源。
Tips:将图片转为webp格式的插件通常在此Task前处理~
过程简述:manifest文件中生成compatible-screens,用于屏幕适配
相关代码:CompatibleScreensManifest.kt
过程简述:合并AndroidManifest.xml文件
相关代码:ProcessApplicationManifest.java、ProcessLibraryManifest.java
过程简述:调用aapt2 link 打包资源并生成R.java文件
相关代码:TaskManager.java → createProcessResTask()
过程简述:编译Kotlin文件为字节码
相关代码:没找到...可能在kotlin插件源码里
过程简述:拷贝lint jar包到指定位置
相关代码:PrepareLintJar.java
过程简述:生成annotationProcessors.json文件
相关代码:JavaPreCompileTask.java
过程简述:编译java文件
相关代码:AndroidJavaCompile.java
过程简述:编译NDK
相关代码:NdkCompile.java
过程简述:合并Renderscript文件(.rs)
相关代码:MergeSourceSetFolders.java
过程简述:编译Renderscript文件(.rs)
相关代码:ShaderCompile.java
过程简述:合并assets文件
相关代码:MergeSourceSetFolders.java
过程简述:验证签名
相关代码:ValidateSigningTask.kt 附加信息:检查当前Variant的签名配置中是否存在密钥库文件,若是当前密钥库默认为debug keystore,那密钥库不存在也会进行相应的建立。
过程简述:编写SigningConfig信息
相关代码:SigningConfigWriterTask.kt
过程简述:检查重复class
相关代码:CheckDuplicateClassesTask.kt 附加信息:检查项目外部依赖是否不包含重复类,打包成dex的时候再检测报错不怎么友好,因此引入了这个Task用于快速失败。
过程简述:将class打包成dex
相关代码:DexArchiveBuilderTransform.java
核心代码解析:
定位到 transform()
方法,能够看到对class的处理分为了两种,目录下的 class和.jar里的class:
跟下 processJarInput()
:
继续跟:convertJarToDexArchive()
对class两种处理方式,最后都走到 convertToDexArchive()
,其中调用了 launchProcessing()
:
这里的 dexArchiveBuilder.convert()
其实就是内部调用dx或d8来打dex,跟下赋值处:
过程简述:打包第三方库的dex
相关代码:ExternalLibsMergerTransform.kt 核心代码解析:
一样跟 transform()
:
建立了一个 DexMergerTransformCallable
实例,而后调 call()
方法:
比较简单,就是调下dx或d8将上面生成的依赖库的dex合并成一个dex。
过程简述:打包最终的dex
相关代码:DexMergerTransform.transform() → mergeDex() 核心代码解析:
跟下 submitForMerging()
:
也是建立了一个 DexMergerTransformCallable
实例,剩余逻辑同上~
过程简述:合并jni lib文件
相关代码:MergeSourceSetFolders.java
过程简述:合并jnilibs
相关代码:MergeJavaResourcesTransform.java
过程简述:去掉native lib里的debug符号
相关代码:StripDebugSymbolTransform.java
过程简述:处理java res
相关代码:MergeJavaResourcesTransform.java
过程简述:合并java res
相关代码:MergeJavaResourcesTransform.java
过程简述:打包APK
相关代码:PackageApplication.java → PackageAndroidArtifact.doTask()
核心代码以下:
而上面的这些updateXxx()方法,调用的都是:IncrementalPackager → updateFiles()
最终调用mApkCreator.writeZip将上述内容写入到APK中。
过程简述:生成混淆文件
相关代码:ExtractProguardFiles.java
上面的Tasks过滤了锚点Task,啥事锚点Task?答:空Task,用来代表处于某种状态。
以 preBuild
为例,全局搜它,定位到: TaskManager → MAIN_PREBUILD
:
跟下引用处:createTasksBeforeEvaluate()
:
注册了一个名为 **MAIN_PREBUILD
**的Task,但没有传闭包(任务内容),即空Task。
小结
以上就是本节的所有内容,看无缺像懂了些什么,又说不出来懂了什么,不要紧,毕竟有点偏理论,为后面Gradle的更深刻学习及应用作铺垫而已,不急,有疑问或文中有误地方欢迎评论区指出,谢谢~
对了,喂,三点几了:
参考文献: