Self Driven

兴趣是1,坚持是剩下的99

0%

android 清理未使用资源

项目中已经很久没有清理资源了,趁着这次删除手机直播的机会再重新研究了下 AS 的 remove unused resource 操作。

关于 RUR

Remove Unused Resources (Refactor-> RUR)是 AS 2.0+ 后加进来用来清除未使用资源的操作。在执行引用的分析之后会列出所有未被使用的 drawable/style/string/layout 资源,统一进行清除。

在 AS 刚增加这个操作的时候曾对项目执行过一次,但删除了识别出的文件之后的大量编译错误让我觉得这个操作并不靠谱。而且这个操作无法针对性的设置白名单,貌似‘ 只能’ 在扫描之后手动 exclude 也让觉得很麻烦。
项目里面需要设置的白名单不少,所有不是通过 R.xx 方式引用的资源,比如通过 getIdentifier 方式获取的,还有通过反射调用 R.java 域的,这些都没法通过查找引用的方式检查出来。

设置白名单

这次趁着删代码的机会再找了下删除资源的好方法,看到了官网提到的 keep resource

如果您有想要保留或舍弃的特定资源,请在您的项目中创建一个包含 标记的 XML 文件,并在 tools:keep 属性中指定每个要保留的资源。…将该文件保存在项目资源中,例如,保存在 res/raw/keep.xml。构建不会将该文件打包到 APK 之中。

像下面这样定义:

1
2
3
4
5
6
7
- keep.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="
@layout/l_used*_c,
@layout/l_used_b*"
tools:discard="@layout/unused2" /> // discard 表示需要删除的资源,不大常用

实操

既然 shrinkResources 会读这个配置,那抱着 RUR 是不是也会的想法尝试了一下。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="
@drawable/face_*,
@dimen/*,
@style/*,
@string/*,
@color/*,
@array/*"/>

这个配置文件会忽略以 face_ 结尾的 drawable 资源跟所有的 style/dimen/string/color/array 未使用资源。
实际执行了一次,扫描结果确实是忽略了这些未使用资源。结果看起来不错,1k 个文件,尝试删除并编译,果然还是报错了。
提示信息是说找不到特定布局跟 id,RUR 的引用判断还是有问题吗?粗略看了出错的几个文件,引用 id 的这个类还在使用,对应的布局不应该被删吧。
先不深究,把被误删的布局加进 keep 文件,然后恢复之前 RUR 删除的文件重新执行 RUR。
注意这里最好不要单独 revert 误删的文件,布局文件里还可能引用其他布局或者资源,在上次清除时可能连带被删,还得再找出这些资源一个个 revert,没准还带递归引用。
这次再跑 RUR 可以正常运行了,随便再在其他分支看了几个被删除的布局也确实没引用,RUR 操作成功了。

深究白名单文件

删完资源再来看看几个被加进白名单的布局。

第一类

尝试搜了其中一个布局,layout_game_room_landscape_bottom。在代码里面搜不到这个布局的引用,但直接删除之后项目就编译出错了,提示找不到对应 id 的资源。
原因 Get,这类布局删掉报错其实就是布局真没再使用,但是 id 仍然在代码里被引用导致的。find 这些 id 的操作实际只会返回 null,但因为加了非空判断,项目里不会崩。后面要做的就是把这块业务对应的代码一起删掉,不能只删布局。

第二类

排除了白名单里上面这类布局之后,还剩下一类布局,这类布局有个共性就是布局只被用在 new XXBaseAdapter(context, R.layout.xxx) 这个构造函数里。只要构造函数传入的布局就会被删除?
仔细想了下不大可能,因为 ArrayAdapter 的构造函数就是 ArrayAdapter(context, layoutId),这可能一下子就会误判很多布局。
另外写了一个 demo 来验证最小被误删除的原型,最后发现是这个:

1
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.layout_simple_remove) {};

如果 R.layout.layout_simple_remove 只在这里被引用,那 RUR 就会提示这个布局未被引用可删除。如果去掉后面的匿名类实现声明,这个布局的引用又能被正常找出来了。bug 无疑,不过竟然没人提过,是不是这操作都没什么人用呢。

资源删除 done,目前看似乎没什么问题,静待回归。