背景
最近这个版本需要降低APK的大小, 所以很多功能需要从主APK中移除到插件中,除了相关工程的代码,还有Assets、Libs、Resources都需要移动到插件中,而在插件拆分的过程中也遇到了很多问题,需要记录一下。
资源标志
通常我们通过来获取字符串:
getResources().getString(R.string.about);
传入的R.string.about
是一个int
类型的值,是AAPT打包时为每个资源文件分配的唯一的ID。在Resources
类中,所有获取资源名称、资源包名、资源ID等等的方式都是通过AssetsManager
对象来获取的。
而从官网中,我们也能知道完整的资源名包含三个部分,而ID对应的也是这三部分:
- Package:App或者插件Apk的包名
- Type:Dimen、Style、Attr、String、Anim等等
- Name:资源名称
通过这三个信息结合,就能唯一标志一个资源了。
AAPT打包机制
在AAPT打包的过程中会将res中除了raw和assets之外的资源(如layout、string、drawable等等)打成二进制文件,并且生成R.java
以及resources.arsc
这两个文件。
R.java
中存放着各个Type对应的id:
public static int Base_Widget_AppCompat_ImageButton = 0x7f16008b;
其中:
前两位代表包:0x7f
后两位代表类型:16
最后四位代表id:008B
而在resources.arsc
中,则保存着这APK的包名,以及资源的映射表。通过Android Studio中可以打开这个文件:

在AAPT打包的时候,默认的包都是0x7f,而且Type都是固定的。
资源查找过程
一般通过Resource.getIdentifier
来获取资源名称对应的ID。
/**
* Retrieve the resource identifier for the given resource name.
*/
/*package*/ native final int getResourceIdentifier(String name,
String defType,
String defPackage);
如果包名错误,类型错误的话,则无法找到资源,会返回id为0
。通过Name,Type,DefaultPakcage组成的资源字符串为:
// package/type:name
com.pakcage.test/string:resource_name
在AssetManager中查找资源Id的过程:
- 遍历已经添加的
AssetsPath
路径 - 根据对应的AssetsPath路径中查找
resources.arsc
资源表,匹配资源表中的包名 - 匹配到后,根据Type来找到对应的资源类型表
- 在资源Type表中匹配Name,找到ID
在标准的Gradle打包中,R.java是不会被修改成常量的,对于资源的引用还是会保持R.string.about
,而不会使用常量的0x7f
来替换。
插件资源路径,也就是插件APK的路径
插件方案
当使用插件,并且资源外置在插件APK中的时候,需要完成以下事情:
- 将插件APK的所在路径添加到
AssetManager
中,以便AssetsManager
可以找到资源路径 - 需要将插件中所使用的资源ID按不同插件的包名进行区分,因为AAPT默认会从0x7f开始进行分配
解决方案有多种:
- 修改aapt,为插件分配不同的pakcageId,然后将引用R.id的地方修改为常量
- 修改aapt过后的R.java文件,合并多个插件的R.java文件,并且重新分配R.java中的id常量,并且重写resources.arsc文件
遇到的问题
- 由于在Manifest.xml中定义的Activity最后还是会merge到主包的Manifest.xml中,所以对于Manifest.xml中引用的string、style、theme等等如果不在主包的话,打包会不通过。
- 在插件的Activity中使用资源的时候需要小心:
- 保证getResource中的Resource是已经添加过插件资源路径的
- 保证资源调用时,Package与Id能够匹配上
网友评论