Android资源学习(四)资源技术使用

Android资源学习系列

Posted by Cc1over on December 31, 2019

Android资源学习(四)资源技术使用

前言

读Android9.0的源码,从资源编译到资源管理器初始化到资源查找,学习了Android对读取的优化,对代码结构的设计,对缓存方式的设计等等,但是其实不管黑猫还是白猫,能用在项目里的才是好猫,所以,笔者才想写一篇Android资源相关技术的使用以及框架,然后持续更新对应框架的源码阅读笔记

# 换肤

动态换肤方案的几个关注点:

  • 无需继承,在Application中init就能使用的设计架构
  • 如果标记View ?
  • 如何实现资源id的对应关系
  • 资源合并:这个是我最想从换肤框架去学习的,那所有的资源文件合并成同一个大文件,自定义资源解析和管理的设计

# 资源热修复

资源热修复的本质其实就是在资源更改之后通过下发补丁包到线上,所以资源热修复其实可以分为2个部分:

  • 补丁包的生成
  • 补丁包下发成功后的处理

补丁包下发成功后的处理

构造一个新的AssetManager,并通过反射调用addAssetPath方法,把这个完整的新资源包加入到AssetManager中,以这种方式得到一个含有所有新资源的AssetManager

方案代表:

  • Instance Run
  • Tinker

构造一个异于0x7f的package id的资源包,然后在这个包里只放包含改变了的资源项,然后直接在原来的AssetManageraddAssetPath添加这个包,处理情况为:

  • 增加资源:直接加入补丁包,然后在新代码里直接引用
  • 删除资源:本地资源包不处理,直接不引用
  • 修改资源:增加资源类似直接加入补丁包,然后引用处改为新id

方案代表:

  • Sophix

利用PackageManagergetResourcesForApplication方法生成补丁包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.arscR.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

接下来我的计划就是做一系列的源码源码解析,从源码里面看实现

参考资料:

记一次苦逼的逆向分析经历

Android开发高手课23 包体积优化(下):资源优化的进阶实践