赞
踩
/ 今日科技快讯 /
近日,谷歌及其母公司Alphabet首席执行官桑达尔·皮查伊接受专访时表示,打击虚假信息是“我们所做一切事情的核心”,并称搜索仍是“终极登月项目”。 皮查伊表示,作为全球最大的搜索引擎,谷歌主要专注于对“真实、准确和安全”的排名进行结果。
/ 作者简介 /
本篇文章来自coder-pig同学的投稿,和大家分享了Gradle打包APK的过程分析,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章!
coder-pig的博客地址:
https://juejin.cn/user/4142615541321928
/ 前言 /
Android Gradle Plugin,简称AGP,老早之前就想好好研究下Android APK的打包过程,毕竟APK包体积优化的前置知识之一。
奈何当时的知识储备严重不足,硬啃着实难受,在学了两周的Gradle后 ,觉得应该有一战之力了,所以这一节来了!
内容较多,建议先收藏,有时间再慢慢细品~
/ 网上流传的三张APK打包图 /
Android官网有一张新的打包流程图(左),相比起旧的流程图(右)更抽象,隐藏了很多细节:
在Android Studio Project Site还找到了一张更详细的图:
如果只是满足于一个写基础业务的Android开发仔,了解下足矣,不过如果想有更深的造诣,还是建议往下学的。
比如面试时(我自己脑补的~)
面试官:简历上写的做过APK体积优化?说说都做了哪方面的优化?
你:从资源大头入手,把png图片都转成webp,使用AndResGuard对资源进行混淆。
面试官:用Gradle写一个webp转换插件,说下思路。
你:不会,我一般右键直接转换...
面试官:那说下AndResGuard的大概原理。
你:
本节就来了解下AGP的构建过程,以及简单了解下APK的打包过程~
Tips:Tasks那么多,不可能一个个去精读源码解析,不同插件版本还有差异,不如授之以渔,本文的目的就是让读者遇到问题时懂得如何追根溯源,找到对应的源码。
/ 如何查看插件源码 /
研究对象是AGP的源码,所以要先搞一份源码,方法有下述几种。
如果磁盘空间比较充足,可以通过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()方法的实现就好。
/ 执行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,然后观察此窗口。
啧啧,还可以看到每个Task的执行时间,不错,但先不跟每个Task具体内容,而是跟下AGP的构建过程~
/ Get /
上一节将Gradle插件时说过,每个插件都会配置一个id名字.properties的文件,在此写上插件的实现类,全局搜定位到下述文件。
打开
指向AppPlugin类,跟下。
上节说过插件类都继承于Plugin,入口函数apply(),但在这里没找到,跟下AbstractAppPlugin → BasePlugin。
行吧,在BasePlugin中重写了apply()方法,里面调用了两个函数,先跟下basePluginApply()。
执行一些检查操作,接着是插件的初始化及配置,而另一个函数pluginSpecificApply()则是空实现,接着跟下配置项目、配置扩展及创建Tasks的过程。
创建DataBindingBuilder实例,强制使用不低于当前所支持的最小插件版本,应用Java插件,如果启用了构建缓存选项,创建buildCache实例,添加了一个回调:所有project执行完后执行资源回收相关操作。
完成下述几项工作:
① 创建build.gradle中的Android DSL;
② 创建VariantFactory、TaskManager、VariantManager实例;
③ 注册新增/移除配置的回调,包括:signingConfig,buildType,productFlavor;
④ 创建默认的debug签名、debug和release两种buildType;
跟下createAndroidTask()。
跟下createAndroidTasks()。
注意下:这里遍历了所有的variantScope,然后调用createTasksForVariantData()创建变体数据对应的Tasks。
跟下createTasksForVariantScope()。
抽象方法,看下哪里实现了这个方法,搜下extends TaskManager。
最终定位到了ApplicationTaskManager类。
噢吼,就是在这里完成APK打包过程的Tasks,可以简单跟跟验证下createAnchorTasks(),创建锚点Tasks。
跟下createVariantPreBuildTask()。
2333,跟上面的APK打包Task链的相呼应,AGP插件的构建过程就跟到这里,接着了解下APK打包的Task。
/ APK的打包过程 /
Tips:分享下搜索Task的实现类的技巧 → 全局搜 "xxx", "yyy" 即可快速定位对应Task类,如 "compile", "Aidl",或者搜索整个Task,然后删删删匹配。
过程简述:将.aidl文件通过aidl工具转换成编译器能够处理的Java接口文件
过程简述:检查AndroidManifest.xml文件是否存在
过程简述:处理Renderscript文件(.rs)
过程简述:生成 BuildConfig.java 文件
过程简述:持久化APK数据到apk-list.gson中
过程简述:遍历res下的values目录下xml文件,生成resValues文件generated.xml
过程简述
- 使用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,用于屏幕适配
过程简述:合并AndroidManifest.xml文件。
过程简述:调用aapt2打包资源并生成R.java文件。
过程简述:编译Kotlin文件为字节码
过程简述:拷贝lint jar包到指定位置
过程简述:生成annotationProcessors.json文件
过程简述:编译java文件
过程简述:编译NDK
过程简述:合并Renderscript文件(.rs)
过程简述:编译Renderscript文件(.rs)
过程简述:合并assets文件
过程简述:验证签名
附加信息:检查当前Variant的签名配置中是否存在密钥库文件,如果当前密钥库默认为debug keystore,那密钥库不存在也会进行相应的创建。
过程简述:编写SigningConfig信息
过程简述:检查重复class
附加信息:检查项目外部依赖是否不包含重复类,打包成dex的时候再检测报错不怎么友好,所以引入了这个Task用于快速失败。
核心代码解析:定位到 transform() 方法,可以看到对class的处理分为了两种,目录下的 class和.jar里的class。
跟下processJarInput()。
继续跟convertJarToDexArchive()。
对class两种处理方式,最后都走到convertToDexArchive(),其中调用了launchProcessing()。
这里的dexArchiveBuilder.convert()其实就是内部调用dx或d8来打dex,跟下赋值处。
过程简述:打包第三方库的dex
核心代码解析:同样跟 transform()。
创建了一个DexMergerTransformCallable实例,然后调call()方法:
比较简单,就是调下dx或d8将上面生成的依赖库的dex合并成一个dex。
过程简述:打包最终的dex
核心代码解析
跟下submitForMerging()。
也是创建了一个DexMergerTransformCallable实例,剩余逻辑同上~
过程简述:合并jni lib文件夹
过程简述:合并jnilibs
过程简述:去掉native lib里的debug符号
过程简述:处理java res
过程简述:合并java res
过程简述:打包APK
核心代码如下
而上面的这些updateXxx()方法,调用的都是IncrementalPackager → updateFiles()。
最终调用mApkCreator.writeZip将上述内容写入到APK中。
过程简述:生成混淆文件
上面的Tasks过滤了锚点Task,啥事锚点Task?答:空Task,用来表明处于某种状态。
以 preBuild 为例,全局搜它,定位到TaskManager → MAIN_PREBUILD。
跟下引用处createTasksBeforeEvaluate()。
注册了一个名为 **MAIN_PREBUILD**的Task,但没有传闭包(任务内容),即空Task。
/ 小结 /
以上就是本文的全部内容,看完好像懂了些什么,又说不出来懂了什么,没关系,毕竟有点偏理论,为后面Gradle的更深入学习及应用做铺垫而已,不急,有疑问或文中有误地方欢迎评论区指出,谢谢~
对了,喂,三点几了!
推荐阅读:
欢迎关注我的公众号
学习技术或投稿
长按上图,识别图中二维码即可关注
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。