Android资源学习(四)资源技术使用
前言
读Android9.0的源码,从资源编译到资源管理器初始化到资源查找,学习了Android对读取的优化,对代码结构的设计,对缓存方式的设计等等,但是其实不管黑猫还是白猫,能用在项目里的才是好猫,所以,笔者才想写一篇Android资源相关技术的使用以及框架,然后持续更新对应框架的源码阅读笔记
# 换肤
动态换肤方案的几个关注点:
- 无需继承,在Application中init就能使用的设计架构
- 如果标记View ?
- 如何实现资源id的对应关系
- 资源合并:这个是我最想从换肤框架去学习的,那所有的资源文件合并成同一个大文件,自定义资源解析和管理的设计
# 资源热修复
资源热修复的本质其实就是在资源更改之后通过下发补丁包到线上,所以资源热修复其实可以分为2个部分:
- 补丁包的生成
- 补丁包下发成功后的处理
补丁包下发成功后的处理
构造一个新的AssetManager,并通过反射调用addAssetPath方法,把这个完整的新资源包加入到AssetManager中,以这种方式得到一个含有所有新资源的AssetManager
方案代表:
- Instance Run
- Tinker
构造一个异于0x7f的package id的资源包,然后在这个包里只放包含改变了的资源项,然后直接在原来的AssetManager中addAssetPath添加这个包,处理情况为:
- 增加资源:直接加入补丁包,然后在新代码里直接引用
- 删除资源:本地资源包不处理,直接不引用
- 修改资源:与增加资源类似直接加入补丁包,然后引用处改为新id
方案代表:
- Sophix
利用PackageManager的getResourcesForApplication方法生成补丁包Resources对象,避免调用AssetManager 中的非SDK方法addAssetPath
但是这个方案要在apk打包的时候把Context相关的组件全部做更换,然后在更换类中修改getResources方法的实现
方案代表:
- Stark
补丁包的生成
补丁包的生成主要分为两种
- 全量:将所有的资源放入补丁中
- 增量:只把发生改变的资源放入补丁中
增量补丁包存在的问题
资源文件我们可以差量的方式把改动过的文件拷贝到补丁apk中,那resources.arsc怎么办呢?如何去生成差量的resources.arsc文件?主要总结方案为:
- 改造aapt:在它收集所有的资源并且生成了R文件之后,我们通过传入一份文件(里面包含改变过的资源索引)来动态的移除ReasourceTable中没有改变过的资源,这样最终生成的arsc文件就是增量的
- 改造aapt:
- 在aapt中存在–split这样一个命令。我们可以通过改造这个命令去实现需求
- 我们都知道Android的系统资源是不会被加到arsc文件中的,具体的aapt命令为-I,那我们是不是也可以通过再造aapt,去把我们没有修改过的资源也像系统资源一样不添加到arsc中呢?
- 我们甚至可以绕开aapt,不用它转而自己去生成arsc文件,主要的逻辑都是ResourceTable.cpp的flatten方法中,可以照葫芦画瓢嘛
# apk包体积优化
资源混淆
- aapt
- AndResGuard
移除无用资源
-
存在问题:当使用shrinkResources在混淆阶段做资源压缩的时候对于无用的资源文件,它会以空文件替换的方式实现,但是resources.arsc中依然会存在这些空文件的路径,并且对于那些无用的String、ID、Attr、Dimen等资源是也会存在resources.arsc中无法移除
-
主要原因:出现这个问题的主要是因为shrinkResources是要依赖混淆实现的,在ProGuard把部分无用代码移除,这样这些无用代码所引用的资源也会被标记为无用资源,在这个过程中,R.java文件已经生成好了,也就是说资源id其实以及定下来了,而id的决定取决于资源收集的顺序,如果在这个时候强行把无用的资源文件删除,resources.arsc和R.java的资源ID都会改变,然而就会出现资源错乱或者做不到的情况
-
其他问题:
其实我们可以解压apk去把那些无用资源文件给删掉,但是如果只是把这些资源文件删掉,resources.arsc中又会存在许多多余而且无意义的数据,但是如果处理resources.arsc就相当要把这个文件的内容重写一遍,所以resources.arsc其实是优化中难度比较高的一个点
-
解决方案:那就是在移除无用的资源相关信息的同时保持之前一定好的ID不会发生改变,而这个方案的实现就在:
Matrix中的RemoveUnusedResourcesTask
无用资源检测
无用资源监测主要有两种方式,第一种方式就是结合shrinkResources然后根据crc的值来做无用资源的检测,第二种就是Matrix中提供的检测方案
crc:
- 其实Android在把shrinkResources中检测到的无用资源文件替换成空文件的时候,它已经提前把空文件的内容和crc校验和的值给做好了,所以针对同一类型的无用文件来说,它被替换后的文件内容与crc值都是一样的,所以就可以根据这个crc的值去找到被替换的文件
public static final byte[] TINY_XML = new byte[] {
(byte) 3, (byte) 0, (byte) 8, (byte) 0, (byte) 104, (byte) 0,
(byte) 0, (byte) 0, (byte) 1, (byte) 0, (byte) 28, (byte) 0,
(byte) 36, (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0,
(byte) 0, (byte) 1, (byte) 0, (byte) 0, (byte) 32, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 1,
(byte) 120, (byte) 0, (byte) 2, (byte) 1, (byte) 16, (byte) 0,
(byte) 36, (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0,
(byte) 0, (byte) 0, (byte) -1, (byte) -1, (byte) -1, (byte) -1,
(byte) -1, (byte) -1, (byte) -1, (byte) -1, (byte) 0, (byte) 0,
(byte) 0, (byte) 0, (byte) 20, (byte) 0, (byte) 20, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0,
(byte) 0, (byte) 0, (byte) 3, (byte) 1, (byte) 16, (byte) 0,
(byte) 24, (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0,
(byte) 0, (byte) 0, (byte) -1, (byte) -1, (byte) -1, (byte) -1,
(byte) -1, (byte) -1, (byte) -1, (byte) -1, (byte) 0, (byte) 0,
(byte) 0, (byte) 0
};
public static final long TINY_XML_CRC = 0xd7e65643L;
以XML这类文件为例….
Matrix:
- 利用了R.txt获取apk中所有的资源
- 读取smali文件中引用资源的指令(reference和直接id引用)得到class中引用的资源
- 通过apktool解析res目录下的xml文件、AndroidManifest.xml以及resources.arsc得到资源引用关系
- 然后根据这三个集合就能拿到无用资源了
- 具体实现在Matrix-> ApkChecker-> UnusedResourceTask
- 当然相对的asset也有UnusedAssetTask
接下来我的计划就是做一系列的源码源码解析,从源码里面看实现
参考资料: