40、资源优化:资源引用分析、无用资源清理、资源国际化、资源适配策略
说到资源优化,我脑子里第一个蹦出来的画面,是几年前接手的一个“祖传项目”。
那个APK解压出来,光图片资源就占了30多兆。里面甚至有十几张完全一样的按钮背景图,只是文件名不同。我当时就一个感觉——这哪是App,这分明是个图库。
资源优化这件事,说白了就是给APK“减脂”。你想想看,用户下载一个App,结果一半流量都花在下载那些根本用不上的图片和布局上,多冤啊。今天我们就聊聊怎么把这部分“赘肉”减掉。
资源引用分析:搞清楚谁在用
动手清理之前,得先知道哪些资源是“活”的,哪些是“死”的。我个人习惯用Android Studio自带的工具来扫一遍。
Lint检查是最直接的手段。在菜单栏点 Analyze → Inspect Code,然后选 Android → Lint → Performance → Unused resources。跑完之后,它会列出一份清单,告诉你哪些资源没有被任何代码引用。
核心思路:资源引用分析的本质,就是建立一张“资源-代码”的引用关系图。从代码入口(Activity、Fragment、布局文件、样式主题)出发,遍历所有R.id、R.drawable、R.string等引用,找出那些没有任何入边的孤立节点。
不过这里有个坑。Lint只能分析静态引用。如果你用 getIdentifier() 或者 Resources.getDrawable() 传字符串拼接的资源名,Lint是查不到的。我在项目中遇到过这种情况:一个动态换肤功能,通过反射加载图片,结果Lint报告说这些图片“未使用”。
注意:对于动态引用的资源,建议在资源文件名上加上统一前缀(比如 dynamic_),然后在Lint配置文件中添加白名单,避免误删。
无用资源清理:该删就删,别手软
分析完了,接下来就是动手删。但别急着全选删除,我建议分三步走:
- 确认引用链:有些资源虽然代码里没直接引用,但可能被主题、样式或者第三方库间接引用。比如
values/themes.xml里引用的某个颜色值,删了可能导致整个UI变色。 - 使用Refactor功能:在Lint结果中右键点击未使用的资源,选择
Refactor → Remove Unused Resource。Android Studio会自动处理相关的R文件引用。 - 清理第三方库的冗余资源:很多第三方库会带一堆用不上的资源。比如你只用了某个图片加载库的缓存功能,但它却带了十几张示例图片。这时候可以用
shrinkResources true配合resConfigs来裁剪。
// build.gradle 中的配置示例
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true // 开启资源压缩
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
defaultConfig {
// 只保留中文和英文资源
resConfigs "zh", "en"
}
}
小技巧:开启 shrinkResources 后,Gradle会在打包时自动移除未引用的资源。但它依赖ProGuard的代码混淆结果——如果代码里某个资源引用被混淆规则误删了,资源也会被连带删除。所以一定要在Release包上做完整的回归测试。
我曾经在一个项目里,因为 shrinkResources 误删了某个启动页的图片,导致线上版本启动页一片空白。嗯,从那以后我养成了一个习惯:每次开启资源压缩后,都会手动对比一下APK里的资源列表,确保没有“误伤”。
资源国际化:别让翻译成为噩梦
国际化这件事,说白了就是让你的App能说“人话”。不同地区的人,说的语言不一样,用的数字格式、日期格式、货币符号也不一样。
Android的资源系统天生支持国际化。你只需要在 res 目录下创建对应的 values-语言代码 文件夹就行。比如:
values/strings.xml— 默认(通常是英文)values-zh/strings.xml— 简体中文values-ja/strings.xml— 日语
但这里有个容易忽略的点:字符串格式化。不同语言的语序不一样。比如中文说“打开文件 %s”,英文说“Open file %s”,看起来没问题。但有些语言会把文件名放在前面,动词放在后面。这时候如果你在代码里写死了字符串拼接,翻译就全乱了。
// 错误做法:字符串拼接
String message = "欢迎 " + userName + " 回来";
// 正确做法:使用占位符
// strings.xml 中定义:<string name="welcome_back">欢迎 %s 回来</string>
String message = getString(R.string.welcome_back, userName);
最佳实践:所有需要拼接的文本,都用 %s、%d 等占位符写在strings.xml里。代码里只负责传参数。这样翻译人员只需要调整语序,不需要动代码。
另外,图片和布局也要考虑国际化。比如某些文化中,红色代表喜庆,但在另一些文化中可能代表警告。我建议把这类“文化敏感”的资源也放到对应的 drawable-语言代码 目录下。
资源适配策略:一套资源,多端适配
资源适配,核心就一句话:用最少的资源,覆盖最多的设备。
Android设备屏幕尺寸、密度、方向千差万别。如果每个尺寸都放一套图,APK体积会爆炸。我的策略是这样的:
| 资源类型 | 适配策略 | 个人建议 |
|---|---|---|
| 图片(位图) | 只提供xxhdpi一套,配合 android:anyDensity 缩放 |
如果对画质要求高,可以再加一套xxxhdpi |
| 图标(矢量) | 全部使用VectorDrawable,一套搞定所有密度 | 注意兼容性,API 21以下需要兼容库 |
| 布局 | 使用ConstraintLayout + 百分比布局,减少多布局文件 | 平板和横屏场景,单独提供 layout-sw600dp 和 layout-land |
| 主题/样式 | 使用 values-night 提供暗黑模式适配 |
颜色值用主题属性引用,不要硬编码 |
这里我想重点说说 VectorDrawable。这东西真的是资源优化的神器。一套矢量图,从手机到电视都能用,而且文件大小只有位图的十分之一甚至更少。我现在的项目里,所有图标都换成了矢量图,APK体积直接少了8兆。
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2V7h2v2z"/>
</vector>
不过矢量图也不是万能的。复杂的渐变、阴影、纹理效果,用矢量图描述起来很费劲,而且渲染性能反而不如位图。我的原则是:扁平化图标用矢量,写实风格用位图。
适配的另一个维度:别忘了 res/raw 和 res/font。字体文件也可以按语言适配,比如中文用思源黑体,英文用Roboto。这样既能保证显示效果,又能减少字体文件体积。
知识体系总览
说了这么多,我把资源优化的核心逻辑画了张图,方便你理解整体脉络:
你看,资源优化其实就这四个维度。先分析清楚哪些资源在用,然后果断清理无用的,接着做好国际化支持,最后用一套聪明的适配策略覆盖所有设备。每一步都不复杂,但合在一起,效果非常明显。
我记得有一次,一个同事问我:“为什么我的App才几个功能,APK却有50兆?”我帮他跑了一遍Lint,发现他引用的一个图片加载库带了30多张示例图片。删掉之后,APK直接降到18兆。他当时那个表情,我到现在都记得。
资源优化就是这样——你花半天时间清理,用户省下的却是每次下载时的几十兆流量。这笔账,怎么算都划算。
公众号:蓝海资料掘金营,微信deep3321