一、技术准备
今天我们来看一下如何修改Android中编译时的资源Id的值,在讲解这内容之前,我们需要先了解一下Android中的资源编译之后的结构和编译过程,这里就不多说了,具体可以查看这篇文章:
http://blog.csdn.net/jiangwei0910410003/article/details/50628894
这篇文章中,介绍了如何解析Android中编译之后的resource.arsc文件,这里就介绍了Android中资源文件编译之后的类型和格式,其实Android中资源编译之后,会产生一个R文件,所有的资源ID都是存储在这个文件中的的,默认我们看到所有的ID都有一个共同的特点,就是他们都是0x7F开头的,其实这个0x7F是包的ID值,我们在在解析resource.arsc文章中提到一点,Android中的id值其实是一个int类型,他的值由三部分组成:PackageId+TypeId+EntryId
PackageId:是包的Id值,Android中如果是第三方应用的话,这个值默认就是0x7F,系统应用的话就是0x01,具体我们可以后面看aapt源码得知,他占用两个字节。
TypeId:是资源的类型Id值,一般Android中有这几个类型:attr,drawable,layout,dimen,string,style等,而且这些类型的值是从1开始逐渐递增的,而且顺序不能改变,attr=0x01,drawable=0x02....他占用两个字节。
EntryId:是在具体的类型下资源实体的id值,从0开始,依次递增,他占用四个字节。
二、遇到的问题
既然我们了解了Android中的资源Id的结构,下面我们来说说我们遇到的问题:
1、在Android项目中偶尔会出现依赖第三方库包,出现资源ID(packageId+typeId+ItemValue)发生冲突的问题(网上有很多解决方案,不一一列举,如public 限定等)。那么对于我们自己提供的库包,如果能指定其包的命令空间(默认是从127=0x7F开始),特别考虑mutiDex的情况,自定义修改package ID显得意义重大。
2、我们在开发Android中插件技术的时候,为了防止插件工程中的资源Id和宿主工程中的资源Id不冲突,也是需要去修改一下插件中编译之后的资源Id值,来减少冲突。
那么上面就是我们遇到的问题,其实我们的解决方案很简单,就是在编译的时候修改资源Id值,给一个限定值。
三、解决思路
我们之前讲解了资源Id的组成结构,发现高两个字节是代表PackageId的值,而且第三方app的默认值是0x7F,那么我们能不能修改这个值呢?比如,插件1中的资源Id中的PackageId为0x30,插件2中的资源Id中的PackageId为0x31...这样每个插件的资源就被划分了一定的区域值,同时保证不要和主工程中的0x7F冲突即可,那么这些值就可以从0x02~0x7E了,这个区间值我们都是可以使用的,为什么0x01不能用呢?因为他是系统应用的呀,所以我们就有0x7E-0x02=124个区间,哈哈,听着好兴奋,那么我们是否可以操作了呢?答案是可以的,我们知道Android中编译资源用的是aapt命令,那么我们就可以查看他的源码来看看是否可以。
aapt命令是Android中提供的编译apk的一个工具,所以源码可以从 Android源码目录/tools/... 下面查看:
这个工具的源码还是不复杂的,没多少文件,当然入口肯定找main啥的关键字了,果然看到一个Main.cpp文件,打开查看,找到入口函数main,这里我们可以看到,他对输入参数做了判断:
这里main函数有点长,我们直接看最后的处理函数:
这里有一个handleCommand函数,这里就是主要处理命令的功能:
这里有好多个函数,但是我们这里需要关注的是doPackage函数,他是打出包的关键,但是这时候我们发现全局搜这个函数,找不到,那么这个函数肯定是被引用的,源码中查找具体函数,
脑补一下:
这里因为是Window系统,不想是Linux系统,可以直接使用find+grep就可以快速的查找到包含指定内容的文件了,但是Windows中提供了可视化的文件搜索,但是他默认在搜索的时候,只是搜索文件名,不搜索包含的内容,所以需要设置一下,可以到文件夹选项中设置:
这时候我们可以在tools目录下搜索了:
这时候看到了,我们搜到了三个文件,Main.cpp可以不用看了,因为已经看过了,那么就在Command.cpp里面了:
这里我们往下面看:
这里有一个方法,而且我们看注释,这里就是编译的核心函数:buildResources,我们在全局搜这个函数,没找到,那么我们还是到整个目录下去搜:
搜到了,在Resource.cpp中:
这里看到,一个packgeType字段,这个就是包类型,这里有三个类型:共享的,系统的,第三方
突然发现这个似乎和PackageId的值有关系,我们接着往下看:
在这里,用到了packageType,而且有一个重要的类型ResourceTable,这个就是资源索引表,和ResId有映射关系的数据结构,所以我们查看他的定义:在ResourceTable.cpp中
我擦,果然,看到结果了,这里看到了有三个值,0x00,0x01,0x7F。说明我们找到核心的地方了。接着往下看:
这里构建了一个Package,这里传入了packageId值的,好了,我们分析源码就到这里了,那么下面我们来看一下源码流程:
首先找到入口类:Main.cpp:main函数,解析参数,然后调用handleCommand函数处理参数对应的逻辑,我们看到了有一个函数doPackage,这里就是处理编译工作的。
然后就搜索到了Command.cpp:在他内部的doPackage函数中进行编译工具的一个函数:buildResources函数,在全局搜索,发现了Resource.cpp:具体查看buildResources函数,发现这里就是处理编译工作,同时在这里我们也看到了核心,构建ResourceTable的逻辑,在ResourceTable.cpp中,也是获取PackageId的地方,到此我们就知道了大体的逻辑,那么知道了逻辑,下面我们就来看看如何修改呢?
其实最好的方法是,能够修改aapt源码,添加一个参数,把我们想要编译的PackageId作为输入值,传进来最好了,其实我们在看源码的时候发现,有一个类型始终传递这,那就是Bundle类型,他是从Main.cpp中的main函数传递到了最后的buildResources函数中,那么我们就可以把这个参数用Bundle进行携带。
四、操作实践
既然知道了修改的思路,下面就是来修改源码了:
第一步:修改Main.cpp中的main函数,获取外部传递的PackageId值,然后存入到Bundle中
这里我们使用的参数是:-apk-module
第二步:我们只需要在ResourceTable.cpp中的构造方法读取这个值即可
到此,我们就修改完了,然后编译,这里编译因为环境不同,所以这里就不列出来如何编译的了,本人使用VC6.0进行编译的,得到了最终的修改之后的appt命令:aapt_win.exe
五、工具使用
那么既然上面我们那么辛苦的修改了aapt命令,下面就可以大展生手的修改一下试一试了,用一个简单的demo进行尝试,不过这里还有一个问题,就是这里我们呀用ant脚本来编译apk,因为我们需要修改aapt命令的路径,换成编译之后的aapt_win.exe,关于如何使用Ant脚本编译apk,这里就不做太多的解释了,大家可以看这篇文章:
http://blog.csdn.net/jiangwei0910410003/article/details/50740026
而且,我就是用这篇文章中的demo做案例的,就是改了一下编译脚本:
修改aapt命令的路径,用我们修改之后的命令
在编译生成R文件的时候,添加参数:-apk-module
编译resource.arsc也需要修改:
这里全部修改成0x78,然后我们跑一个ant脚本:ant release
然后看一下R文件的内容:
哈哈哈,这里我们看到修改成功了,不相信的话,我们可以用我们之前写的一个工具:解析Resource.arsc文件的工具类,打印看一下结果(不了解的同学可以查看这篇文章:http://blog.csdn.net/jiangwei0910410003/article/details/50628894):
打印结果也是正常的,好吧,下面再来看一下,我们使用动态加载来加载这个apk(具体代码这里不粘贴了,不清楚的同学可以查看这篇文章:http://blog.csdn.net/jiangwei0910410003/article/details/48104581):
首先我们可以查看log信息,我们在代码中使用反射去获取一个插件apk中的app_name字段的id值。
日志显示的id是0x78050000,显示的值也是正确的,这样我们就让插件的ResId的范围区分了宿主工程中的0x7F,就不会出现资源冲突的问题了,看一下运行的效果:
好了,到这里,我们就说完了本章的内容了。
aapt修改之后的源码和工具下载地址:http://download.csdn.net/detail/jiangwei0910410003/9454867
六、学习到技术点
1、学习了如何在Windows中查找源码内容
2、学习了aapt编译的整体流程
3、学会了修改编译的资源id值
七、解决的问题
通过修改aapt源码,来达到我们可以随意定制编译之后的resId值,解决我们在引用第三方包或者工程以及在开发插件化的时候遇到的资源id值冲突问题,所以这里就记住一点,我们可以修改Android中编译之后的资源ID值了。
八、总结
结束了这篇文章,感觉收获还是很多的,起码我们知道Android中编译之后的资源ID是可以定制的,虽然有的同学可能现在用不到这个功能,但是我相信迟早有一天你一定会用到的,所以只要记住有这个技术方案就好了。
PS: 关注微信,最新Android技术实时推送