背景:想在编译时按照配置文件修改xml中的特定字符串
android编译时,会在mergeResource任务中把三方依赖库、依赖module、自己的资源进行合并、汇总,最后保存到app/build/intermediates/res/merged/中,processResource的时候,从这个目录中拿数据,进行打包、链接等操作
AAPT时代
aapt时代,mergeResource之后,会在app/build/intermediates/res/merged/中暂存资源,xml文件也以xml的形式直接保存。只需要编写gradle插件,在mergeResource之后修改这些xml即可。processResource任务执行时,已经拿到的是修改后的xml
AAPT2时代
aapt2时代,mergeResource之后会在app/build/intermediates/res/merged/中暂存资源,但是这些资源也不再是普通的xml等文件,而是经过AAPT2编译后的二进制flat文件,这些文件修改起来没有xml那么简单。
开启了aapt2该怎么办
mergeResource负责把各个依赖的资源汇总出来,放到一起,processResource中会进行执行aapt2 link,所以只要在link之前,把app/build/intermediates/res/merged/中的flat修改/替换成我们要的文件即可
方案1
修改aapt2源码,让他在compile的时候做处理。(可行吗?类似atlas aapt)
方案2
解析flat二进制文件,然后重新写入(貌似是比较通用的方案)
方案3
debug了mergeResource源码发现mergeResource之后,会在app/build/intermediates/incremental/mergeReleaseResources/merger.xml生成一个文件,存放了资源情况,里面记录了所有资源的本地路径(在电脑中的)、所属包等情况。那么就可以解析这个文件,把资源copy一份,然后修改。最后把这些资源用aapt2进行一次主动编译,覆盖打包自动生成的那些flat文件即可。(看着可行,具体需要实践)
Debug Code
贴几个点,具体还是自己走一遍代码,才会有深刻的印象。
- 给MergeResources的doFullTaskAction打一个断点,这里必会走
for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(getILogger());
//里面把各个文件解析一下,比如items类的解析成item,创建成ResourceFile,layout类的不解析。最后要checkItems检测是否重复
merger.addDataSet(resourceSet);
}
- ResourceSet中读取每一个xml文件createResourceFile,创建成ResourceFile
根据type来,列入values.xml这种文件需要解析成item进行合并。xxx.layout这种不用解析
ResourceSet中processNewResourceFile应该会把每个item添加到mItems中
for (String dataItemKey : dataItemKeys) {
//遍历这些item
//如果是style开头的需要merge
//这里竟然用到了goto语句,牛逼。。。
setLoop: for (int i = mDataSets.size() - 1 ; i >= 0 ; i--) {
break setLoop;
}
}
- writeAdditionalData方法会把所有的资源写在一起 ,跟标签是merge,包括item、style、layout、attr、图片。文件类型、来源等等。主要是item类型会罗列出来,文件类型如layout、图片等,形成file节点,存放在/rootProject/app/build/intermediates/incremental/mergeDebugResources/merger.xml
- 以上,mergeResource就结束了
- 断点过程中观察app/build/intermediates/res/merged/目录,发现走到doFullTaskAction的最后一行了,目录还是空的,但是放开之后,flat文件就生成了。直接追代码还不好追。
- 在mergeResource的doFullTaskAction方法中断点观察路径,发现destinationDir就是merged目录,那想办法看这个目录在哪用了
- MergedResourceWriter#mRootFolder就是那个目录,Writer中搜rootFolder,发现构造了一个CompileResourceRequest。
- mResourceCompiler.submitCompile(request);构造好之后,还进行了submit。
- 给这里打断点,跑程序,发现确实这里for循环了所有的资源,说明这是编译的入口。
- 直接点源码进去WorkerExecutorResourceCompilationService,发现添加到requests集合中了,一看就是一个生产者消费者模式。
- WorkerExecutorResourceCompilationService有一个成员变量workerExecutor,顾名思义,执行者
- 点进去,果然有submit方法,可能他的实现,用了futures,执行编译任务。那具体的实现在哪呢
- 看WorkerExecutorResourceCompilationService的注释Resource compilation service built on top of a Aapt2Daemon and Gradle Worker Executors.
- 搜Aapt2Daemon,是一个守护进程,看方法就知道,编译操作都在这里完成。
- 之后的源码都没法直接点,as上scroll from source,滚到这个文件处,发现aapt2编译相关源码都在这个包,那就好找了。
-
最后附上一张图,编译就是执行这个命令
以上用了将近半天的时间,收获不小,也了解了一些编译细节。debug源码还是很重要,相对读源码来说,要简单多了,要经常实践。如有错误,欢迎指正。