没用的代码可以删除, 没用的资源文件当然也可以删除。只需要设置 gradle 属性 shrinkResources 为true 即可启用该功能。
build.gradle
Java
android { ... buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
android { ... buildTypes { release { minifyEnabledtrue shrinkResourcestrue proguardFilesgetDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
如果有些资源被误删了,在代码中有使用(通过反射),则可以通过 tools:keep 属性来保留这些资源。为了方便管理,还可以创建一个单独的文件来保留所有需要保留的资源,类似于 ProGuard 的配置文件:
res/raw/keep.xml
XHTML
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" />
<?xmlversion="1.0" encoding="utf-8"?> <resourcesxmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" />
还可以使用 tools:discard 属性来删除之前保留的属性
res/raw/keep.xml
XHTML
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="safe" tools:discard="@layout/unused2" />
<?xmlversion="1.0" encoding="utf-8"?> <resourcesxmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="safe" tools:discard="@layout/unused2" />
在 tools 网站 上 详细解释了如何通过log 查看资源清理以及 safe 和 strict 清理模式的区别。
当然了,如果你发现有大量的资源文件没有用, 为了保持文件整洁,你最好还是手工自己把这些文件删除掉吧。
各种第三方库都把字符串资源翻译成了各种语言,例如 Support Library 和 Google Play Services 就支持几十种语言。但是在你的应用中可能只支持一种或者几种语言,可以通过 resConfigs 选项来告诉编译器,你的应用只支持这些配置的语言,其他语言资源不会被编译到最终的 apk 文件中:
build.gradle
Java
android { defaultConfig { ... resConfigs "en", "zh" } }
android { defaultConfig { ... resConfigs "en", "zh" } }
上面的配置只保留中文和英文资源文件, 其他资源文件都不会包含在最终的 apk 文件中。
注意,在 resConfigs 中只能定义一种屏幕密度属性,对于不同屏幕密度的处理可以使用后面即将结束的 分屏幕发布不同版本的功能。
本节讨论的问题一般是针对大型项目的,这些项目使用了成千上万个资源文件。
如果你发现 resources.arsc 文件占用的空间非常多,多的不太合理,则很有可能就是松散配置项引起的。 下面通过例子来看看引起该问题的原因:
假设在默认的字符串配置文件 (values/strings.xml)中有 5 个字符串。这 5 个字符串的值会定义在一个字符串池中,每个字符串的标识符和对应的池地址会保存在其他地方。那么这 5 个字符串 最后编译到 resources.arsc 中的内容类似如下所示:
Java
String pool: "My App", "Hello", "Exit", "Settings", "Feature" Default config: string/myapp 0x00000001 string/hello 0x00000002 string/exit 0x00000003 string/settings 0x00000004 string/feature 0x00000005
String pool: "My App", "Hello", "Exit", "Settings", "Feature" Default config: string/myapp 0x00000001 string/hello 0x00000002 string/exit 0x00000003 string/settings 0x00000004 string/feature 0x00000005
现在,假设你在应用中添加了一个新的功能,并且该功能仅仅在 api 21+ 之上存在。该新功能需要显示一个不同的字符串内容,那么你选择在 values-v21/strings.xml 中添加一个字符串。
仅仅添加了一个新的 v21 字符串,则 resources.arsc 的内容将会变成下面这个样子:
Java
String pool: "My App", "Hello", "Exit", "Settings", "Feature", "New feature" Default config: -v21 config: string/myapp 0x00000001 NO_ENTRY string/hello 0x00000002 NO_ENTRY string/exit 0x00000003 NO_ENTRY string/settings 0x00000004 NO_ENTRY string/feature 0x00000005 0x00000006 ========== ========== Config size: 20 bytes **20 bytes!**
String pool: "My App", "Hello", "Exit", "Settings", "Feature", "New feature" Default config: -v21config: string/myapp 0x00000001 NO_ENTRY string/hello 0x00000002 NO_ENTRY string/exit 0x00000003 NO_ENTRY string/settings 0x00000004 NO_ENTRY string/feature 0x00000005 0x00000006 ========== ========== Configsize: 20 bytes **20 bytes!**
每个配置项都占用了所有资源文件的空间,虽然实际上在 v21 中其他字符串指向的地址为 null 但是仍然会占用同样的字节数。每个标识符 4个字节。
如果你的应用中有很多不同的配置项,比如 -v21、 -land 、 -en-land-v21 等。则会多占用不少的空间。
假设在实际场景中,一个应用有 3500 个字符串,但是有一个特殊的字符串单独定义在另外一种配置项中,然后改字符串需要翻译为其他 50 种语言(也就是说将会有类似 values-en-land, -pl-land, -de-land, -fr-land… 等 50个目录),这样会多占用如下的空间:
4 bytes * 3500 null entries * 50 languages = 700 kilobytes
如果你把这个特殊的字符串给删除了,则就可以节省 700KB的空间。 有些大型项目仅仅删除了 3 个资源就可以节省 2.5M 的空间。
针对这种特殊的情况,如果你发现仅仅几个字符串的添加就导致 apk 文件变得很大,则你可以考虑在默认配置项中定义这些特殊的字符串,然后在运行的时候,根据系统配置动态的选择使用哪个字符串。
资源 id 一般也有很长的名字,比如 xxx_xxxx.png ,如果可以像混淆代码一样把资源文件也给混淆了,则同样可以减少 resources.arsc 文件的大小,可以使用 DexGuard 或者 微信团队的 资源混淆器 来实现这个功能。关于资源混淆的原理,可以 参考这里 。