先交代一下背景。前阵子我们团队准备发新版本,临近提测的时候,负责投放的同事跑来问我:"APK又大了,现在打出来的包都快90MB了,渠道那边反馈下载转化率掉了,能不能想想办法?"当时我打开Android Studio看了一眼最新构建产物,心里其实挺有数的——这不是一天两天堆出来的问题,是过去半年不断加功能、加依赖、加资源,却没人对包体做任何治理的结果。于是花了大概两周时间,把APK从88MB干到了51MB,整体瘦了差不多42%。这篇文章就是把那两周做的事、踩的坑、以及沉淀下来的日常机制,一次性整理清楚。
文章会比较长,但你可以直接按目录跳着看。我尽量按照"先搞清楚现状,再动手优化,最后建立防止反弹的机制"这个顺序来写,所以即使你是第一次接触包体积优化,跟着走也能完整落地。
1. 包体积不是"技术债",是真金白银的成本
很多开发同学对APK体积的第一反应是:大点就大点,现在Wi-Fi这么快,流量也不贵。这个想法在圈内其实非常普遍,但一旦你把包体积放到真实业务场景里去算账,结论完全不一样。我先把这块说透,因为如果认知不到位,后面定优化目标、争取排期、跟产品对峙的时候,你都站不住脚。
1.1 一个APK的大小到底影响了什么
第一是下载转化率。Google Play曾在官方博客公布过一组数据,APK每增加6MB,下载转化率会下降约1%。国内应用市场虽然没公布过类似曲线,但我在两个渠道后台比对过自家应用的数据,规律是接近的:安装包越大,用户在应用详情页点击安装的意愿越低,特别是那些包体超过100MB的应用,在非Wi-Fi场景下几乎是被直接劝退。
第二是更新成功率。很多人只盯着从0到1的安装,忽略了从1到1.1的升级。增量更新或者全量更新的成功率,跟包体是负相关的。包越大,下载到一半被系统杀掉、用户主动取消、存储空间不足导致安装失败的概率就越高。我们的版本更新率在过去一个季度一直往下掉,后来把包体降下来之后,更新成功率的回升是肉眼可见的。
第三是CDN带宽和服务器成本。这个可能中小企业感受没那么深,但用户量一旦上来,每多1MB的包体,乘以每日下载量,再乘以每GB流量成本,一年下来就是一笔不小的开销。对于出海产品,这一项的开销会更大。
第四是内部效率问题。一个90MB的APK,从CI打包到上传分发平台,再到测试下载安装,整个链路都是时间成本。尤其是测试同学需要频繁切换版本验证Bug时,包体越小、下载越快,整个团队的研发节奏都会舒服不少。
所以,包体积优化本质上不是"洁癖型"工程改善,它直接关系到投放效果、用户留存、运营成本,是业务层面的硬指标。只有先把这个认知达成一致,你后面去推动优化、争取资源时,才能得到产品和管理层的配合。
1.2 优化前必须先定基线
我在动手之前,做了一件事:把最近10个线上版本的大小变化列了一张表,看清楚包体是哪些版本涨上去的,分别在哪个模块上涨的。这样做的目的有两个:一是找到包体膨胀的时间节点,方便追溯是哪次需求/哪次依赖升级引入的问题;二是为后续优化设定一个可量化的目标,比如"在下一个版本里把APK体积压到60MB以内"。
这里有一个关键动作:要让包体积优化可持续,不能只靠一次大扫除,还得从流程上定基线。我在后面第5章会专门展开讲日常保障机制,这里先提一个最基础的建议——每次发布之前,用脚本把新APK的各个组成部分(dex、res、assets、so库)的体积和上个版本做一次diff,任何超过阈值(比如单模块增重5%)的变更都需要说明原因,否则不允许合入发布分支。
2. 先体检再动刀:APK内部到底装的什么
在没有工具辅助之前,很多开发者对APK体积的判断是"感觉"级别的——觉得图片多就压图片,觉得代码多就混淆代码。但真正的优化应该从一张体检表开始,搞清楚APK的每个组成部分各自占了多少空间,然后再决定从哪里下手。
一个标准APK的构成其实很简单,解压后无非就是这几块:classes.dex(Java/Kotlin字节码)、resources.arsc(资源索引表)、res/(编译后的资源文件)、assets/(原始资源)、lib/(native库,按ABI分目录)、META-INF/(签名和清单信息)。优化动作基本都是在跟这几块打交道。
2.1 Android Studio自带的APK Analyzer怎么用
首选工具是Android Studio自带的APK Analyzer,它在老版本里叫APK Analyzer,新版本里入口藏在Build菜单下(Build > Analyze APK...)。选择APK文件之后,它会展示一个非常直观的文件占比图,你可以直接看到lib/占多少、res/占多少、classes.dex占多少,还能继续点进去看每个子目录和单个文件的大小。
有几个比较高频的查看维度:
- 原始文件大小 vs 下载大小:APK Analyzer页面会同时展示APK的原始大小和预估下载大小(即经过压缩后的传输大小)。这两个指标都要关注,因为部分渠道包推广时按下载大小计费,而用户在应用市场看到的"包体大小"也是下载大小。
- 按文件类型排序:可以快速筛选出占用空间最大的文件,通常排在最前面的都是图片、so库和比较大的资源文件。
- 查看classes.dex的构成:APK Analyzer能反查dex里的类列表,虽然功能没那么强,但至少能让你定位到"这个50MB的dex里到底装了哪些包名下的类",对排查超大依赖非常有用。
2.2 命令行工具apkanalyzer
Android Studio的图形界面工具适合单次人工分析,但如果你要做常态化监控,建议直接用命令行版本。Android SDK里内置了apkanalyzer工具,路径在$ANDROID_HOME/cmdline-tools/latest/bin/apkanalyzer。我平时最常用的是这几条命令:
# 查看APK基本信息,包括版本、SDK版本等 apkanalyzer apk summary app-release.apk # 按文件大小列出APK内容,从大到小排序 apkanalyzer files list app-release.apk # 查看dex文件中的类摘要 apkanalyzer dex packages app-release.apk把apkanalyzer files list的输出接一个sort和管道,就能快速生成一份按大小排序的APK内部文件清单。把这个清单用在CI里做包体变化监控,比每次手动打开Android Studio点来点去高效得多。
2.3 我们当时体检出的问题
用APK Analyzer对88MB的旧包做了一次全量体检之后,问题已经很清晰了。我贴一份脱敏后的数据给你参考,你可以用它和你们自己的包体分布做对比:
| 组成部分 | 体积(MB) | 占比 | 问题定性 |
|---|---|---|---|
| res/ | 34.2 | 38.9% | 大量PNG未压缩,多套语言资源冗余 |
| lib/ | 26.4 | 30.0% | 三套ABI重复打包,含两个大型native库 |
| classes.dex | 14.8 | 16.8% | 存在大量无用代码与重复依赖 |
| assets/ | 8.6 | 9.8% | 内置了一份旧版离线数据包,可下架 |
| resources.arsc | 1.8 | 2.0% | 字符串和ID资源膨胀 |
| META-INF及其他 | 2.2 | 2.5% | 正常签名信息 |
这个表一眼就能看出来,res和lib是最大的两块,加起来快70%。所以后面我的优化顺序也很明确:先啃资源,再啃so库,最后再对dex做精细化治理。很多团队一上来就纠结代码混淆和R8规则,反而忽略了rc里那30多MB的图片——这条路其实是反的。
3. 第一步先啃资源:图片、语言、冗余文件
资源是APK体积优化里见效最快、性价比最高的部分。几乎不需要动代码逻辑,只要把资源配置理顺,一个8MB到15MB的体积下降是很常见的。资源优化我拆成五个动作,每一个都是可以直接落地的。
3.1 开启shrinkResources和resConfigs
Android Gradle Plugin里有两个开关,建议每个项目都打开。第一个是shrinkResources,它会对未被代码引用的资源做移除。第二个是resConfigs,只保留你声明支持的语言和屏幕分辨率对应的资源。
android { buildTypes { release { minifyEnabled true shrinkResources true resConfigs "zh-rCN", "zh-rTW", "en" } } }需要注意三点。一是shrinkResources必须配合minifyEnabled一起使用,因为它依赖R8/ProGuard生成的资源引用信息来判断哪些资源没被引用到,只开shrinkResources不开minifyEnabled是不起作用的。二是resConfigs会丢弃你未声明的语言资源,如果后面往代码里新增了多语言支持但忘了在这里补充配置,会出现运行时资源找不到的问题,所以语言列表务必要跟产品实际支持的语言保持一致。三是如果用到了第三方SDK,默认情况下各SDK自带的多语言资源也在裁剪范围内,这可能导致SDK内部的某些文案在特定语言下回退到默认英文,这个问题通常可接受,但如果你的产品对多语言要求极高,需要逐个SDK确认。
3.2 用Lint找出正式没用的资源
shrinkResources是在打包时静默移除未被引用的资源,但有些资源是"通过反射或者字符串拼接被引用"的,AGP的静态分析识别不到,会被当成无用资源移除掉,然后运行时崩溃。为了安全起见,正式的流程里我建议先用Android Lint跑一遍扫描,人工确认移除清单。
在Android Studio里右键模块目录,选择Analyze > Inspect Code,系统会列出lint报告,其中包含了UnusedResources(未使用的资源)警告。也可以在命令行直接跑:
./gradlew :app:lintReleaselint报告会生成在app/build/reports/lint-results-release.html,里面可以按资源类型浏览所有被标记为无用的资源文件,标出引用位置。我们当时用lint扫出将近600个无用资源,涉及各类drawable、layout、values文件,加起来接近6MB,这些都是历次改版迭代后遗留的"尸体"。
不过lint对动态反射的引用识别能力有限。比如你项目里通过context.getResources().getIdentifier("ic_icon_" + type, "drawable", context.getPackageName())这种方式去取资源,lint很可能误报。这种场景建议在lint配置里加上tools:ignore="UnusedResources"或者调整Lint规则,避免误删。
3.3 图片压缩:从PNG到WebP
我们项目里的res目录有34MB,其中占大头的基本都是启动页、banner、引导图这类大尺寸PNG和JPG。这里的优化手段按性价比从高到低排是:
- 转WebP:Android 4.0(API 14)以上就原生支持WebP,现在这个门槛对绝大多数应用来说已经不是问题。WebP在同等画质下比PNG小25%到34%。Android Studio里选中图片右键,Convert to WebP,直接批量处理整套资源。
- 使用矢量图:对于图标、插画这类单色系的图形资源,直接用VectorDrawable(矢量图),体积可以小到忽略不计。但要注意矢量图首次渲染会有CPU计算开销,列表页高频使用的复杂图标需要权衡。
- 压缩工具预处理:如果WebP之后仍需要保留PNG格式(例如某些第三方SDK对资源格式有硬性要求),建议在打包前用tinypng或pngquant批量压缩一遍,通常能把图片体积再削掉一个量级。
有一点必须提醒:启动页、背景图这类大图资源,不要直接塞在res/drawable里。正确做法是放到res/drawable-nodpi/或assets/,避免因为不同分辨率的屏幕密度生成多个副本。很多APK的体积膨胀就是这么来的,一张1080P的大图在drawable-xxhdpi、drawable-xhdpi、drawable-hdpi下各存了一份,直接三倍起。
3.4 Resource混淆与资源合并
如果你的项目已经比较成熟、资源命名混乱,可以引入资源混淆工具来进一步压缩resources.arsc和资源路径。业界目前用得最多的是腾讯的AndResGuard,它核心做的事情是把资源路径从可读(比如res/drawable-xxhdpi/splash_bg.png)改成一个极短的字符串(比如res/drawable-xxhdpi/a.png),同时合并掉大量重复或冗余的资源项。
在接入时有一个比较重要的坑:AndResGuard会对resources.arsc里的资源名做混淆,但如果你在代码里用getIdentifier()动态获取资源,或者某些第三方SDK内部用固定路径访问资源,混淆后就会找不到资源。接入前需要把所有动态引用资源的点全部改成静态引用,或者将相关资源加入白名单。这个工作量说大不大,说小不小,建议作为资源优化的最后一步来做。
另外,如果你们的项目已经切换到Android App Bundle,资源混淆做不做其实影响不大了,因为在Google Play上使用App Bundle时,系统本身已经会对不同设备的资源做按需下发,这里不再展开。
3.5 真实数据对比
以我们的项目为例,资源优化这一轮跑完,res从34.2MB降到了19.6MB,差不多砍掉15MB。其中resConfigs裁剪多语言资源贡献了约3.8MB,lint清理无用资源贡献约5.9MB,转WebP和压缩大图贡献约4.9MB,资源混淆又挤掉了一部分水分。整个过程完全没改任何业务代码,所以风险相对可控,测试回流成本也比较低。
4. 再啃硬骨头:lib目录与so库精简化
如果你的APK体检表里lib目录占比超过20%,那不用怀疑,so库是体积的大头。so库这一块的问题比较特殊,它不像资源那样清理完就一劳永逸,因为涉及到兼容性、运行时加载和第三方SDK的深水区,处理起来要谨慎。
4.1 先理解so库为什么这么大
lib目录下的每个ABI文件夹(armeabi-v7a、arm64-v8a、x86、x86_64)里都装着一整套native库,也就是说,一套源代码被编译成多种CPU架构的机器码,打包时全部塞进了APK。如果你的项目引用了某个体积较大的native库(比如FFmpeg、OpenCV、游戏引擎运行时),心里要有个概念:每多支持一种ABI,包体就会加上一套库的体积。4个ABI齐全的话,光是支撑同一种能力,包体就要乘以4。
4.2 用abiFilters切割ABI
在保证设备兼容性的前提下,可以通过abiFilters指定只保留部分ABI。目前的Android设备市场,x86和x86_64架构的设备基本绝迹了,armeabi(v7a之前的纯armeabi)更是可以彻底放弃。对我们产品来说,只需要关心arm64-v8a和armeabi-v7a。
android { defaultConfig { ndk { abiFilters "arm64-v8a", "armeabi-v7a" } } }顺带一提,如果你们的应用minSdkVersion已经比较高了(比如API 21以上),机型和厂商新机型的覆盖率都在arm64-v8a上,可以更激进一点,只保留arm64-v8a,APK体积还能再往下走几MB。但这件事取决于你们的用户画像,最好用平台给出的设备分布数据说话,别拍脑袋。
开abiFilters之后,还有个连带动作要做:检查build.gradle里是否还有其他途径把全量ABI打进来。有些第三方SDK会在自己的Gradle Transform里强行往APK里塞全量so,abiFilters管不住它们。我们当时就踩过一个坑:明明在abiFilters里只写了arm64-v8a和armeabi-v7a,打出来的包在x86目录下还是躺着一个几MB的so,排查半天才发现是某个bugly类SDK在manifest里注册了自己的so目录,绕过了AGP的ABI过滤,最后只能手动在打包脚本里加了一个剔除步骤。
4.3 大so库的压缩与运行时解压
有些so库属于低频使用场景,比如扫码、图像处理、音视频编辑,这些能力并不需要在应用启动时就全部加载。如果你把这类so库放在assets目录下(或者以压缩形式放在lib目录下),在用户第一次使用该功能时再解压到应用私有目录,然后加载,包体可以省出一大块,代价是首次进入某个功能会有短暂的解压耗时。
不过这种做法有一个前提:你的应用进程需要在部分功能运行时动态加载so库,这要求so库不能被系统的nativeLibraryDir机制管束。具体来说,需要把so文件以libName.so的形式放在assets里,运行时代码里手动把它复制到context.getDir("libs", Context.MODE_PRIVATE)目录,然后用System.load(absPath)加载。这种方式跟Google Play的上传审核政策会有一点冲突,如果完全走Google Play渠道,需要仔细核对政策是否允许,国内渠道一般没有这个问题。
4.4 大型SDK选型要提前算包体账
lib体积膨胀还有一个隐形推手:为了某一两个功能引入的重型SDK。举个典型例子:某个二维码SDK自带zxing相关的so库,但实际上你只用了core模块的扫码能力;某个视频处理库可能只调用了一个滤镜功能,但它捆绑了整套FFmpeg。在做技术选型时,强烈建议把"SDK对包体的贡献"和"SDK对内存的贡献"一起加入评估维度,而不只是看功能和稳定性。
我们当时在lib里发现有一个接近10MB的native库是某个老牌支付SDK带来的,但实际上我们的支付场景根本用不到它那部分native能力。后来通过SDK的Gradle配置,用一个exclude或者切换SDK模块的方式把它摘掉了,包体立刻掉了8MB多。
给一个方法论:每次引入新SDK之前,在Demo工程里打包一次,专门对比引入前后的APK体积增量。数字摆出来,由产品和技术一起决定要不要为这个功能接受这个体积代价。
5. dex与assets的精细化治理:抠出每一粒肉
做完资源和lib之后,包体已经有明显下降,但离目标可能还有距离。这个时候要继续往下压,就得对代码层、assets层动手了。这里的操作需要一定的分析能力,但也是最能体现优化水平的地方。
5.1 R8与ProGuard的正确配置
在release构建中,minifyEnabled true结合R8混淆和裁剪,是代码瘦身的基础能力。很多项目虽然开了混淆,但规则配置非常宽松,导致大量代码没有被裁剪掉。我在看依赖体积问题时发现,不少团队把-keep规则的粒度写得太大,比如一刀切 keep 整个第三方包的所有类,这相当于没有开启任何裁剪。
R8的正确用法是:
- 只保留被反射调用的类、注解和接口;
- 只保留AndroidManifest里引用的四大组件;
- 不要为了省事直接
-keep class com.某家大SDK.** { *; },而应该精确到某个被反射的类或者资源。
另外,升级到高版本AGP之后,R8已经是默认的代码压缩器(替代了ProGuard),建议把老的ProGuard规则文件按R8的语法重新梳理一遍。R8对规则的处理比ProGuard更严格,很多在ProGuard里能跑通的宽泛规则,在R8里会直接报错或者导致裁剪失效。
5.2 重复依赖与无意义依赖排查
代码层的第二个体积黑洞是重复依赖。怎么发现?用Gradle官方自带的dependency分析,或者直接在AS里执行:
./gradlew :app:dependencies --configuration releaseRuntimeClasspath输出结果会比较长,我一般会重点看这些信号:
- 同一个库出现了多个版本(比如guava有12.0和19.0同时存在,R8可能会同时保留两份);
- 同一个库通过不同坐标重复引用(比如Google的gson和kotlinx.serialization同时存在,且都没有进行互相排除);
- 显式引入了SDK中已传递依赖的库。
我们在排查中发现了三个典型问题:一个是两个图片库(Glide和Fresco)同时存在于项目里,它们的基本能力高度重叠,最终在统一到一个库之后,光这一项就瘦了好几MB;另一个是同一个网络库的旧版本和新版本同时存在,被不同SDK引用,导致dex里同时存在两套实现;还有一个是某个内部工具库引用了整个Apache Commons包,而我们只是用它里面的一个字符串转数组的工具方法。
这种问题不是一次性能解决完的,建议每两个迭代做一个"依赖健康检查",把无意义依赖尽早清理掉。
5.3 assets目录里的隐藏大户
assets目录是另一个容易被忽视的区域。因为开发者习惯把一些无法编译成资源的文件(离线数据包、字体、WebView静态资源、预置JSON配置)直接塞进assets里。资产不经过任何编译压缩,所有放进来的文件基本就是最终的包体大小。
我们当时的assets里有三样东西:一个4.8MB的城市选择器离线数据包(JSON格式)、一套2.5MB的PDF模板、一份1.2MB的明文配置文件。这三样东西都是早期业务为了"快速实现"而做的本地内置,但上线后实际使用频率极低。经过产品确认后,将城市数据包和PDF模板切换成了运行时按需下载,配置合并成600KB的轻量格式,assets直接从8.6MB降到1.8MB。
assets优化的核心原则是:只有那些必须在第一时间离线可用的文件才值得放进APK,其他能按需下载的、能删掉历史版本的,一律不要打包。一个非常实用的验收标准是:站在新用户的角度思考,冷启动过程中真正需要读取的文件是哪些,其余的就是候选的删除对象。
5.4 动态功能模块:何时值得上
如果你的应用已经做到包体优化的极限,但业务模块非常多、更新频率又不一致,可以考虑Android App Bundle的动态功能模块(Dynamic Feature Module)。它允许按功能拆分模块,用户只有在触发相关功能时才下载对应模块的代码和资源。这样可以大幅压缩首次安装包体。
不过这个方案的成本也很明显:需要重构项目模块结构、处理动态模块之间的依赖关系,并且要求应用市场支持App Bundle(国内渠道的历史兼容要做评估)。对于中小团队,我的建议是先把本章前面几个常规动作全部做完,如果包体还是压不到目标线,再评估动态模块方案。
6. 后续不反弹的保障:把包体积优化固化到日常
包体积优化最尴尬的结局是:这个版本压下去了,下一个版本因为一次依赖升级、一个临时需求、一个拍脑袋的SDK接入,又涨回去了。所以我在做完本轮优化之后,把更多的精力放在了建立持续保障机制上,确保这个问题不会再次失控。
6.1 在CI里加一道包体监控门禁
首先,在CI流水线中加入打包后的体积diff检查。实现起来不算复杂,可以在打包完成后,用脚本读取新APK的文件构成和大小,和历史基线做对比,超过阈值就让任务失败。这个阈值可以分两层:
- 总量阈值:比如整个APK相对上一次发布版本的增量不超过2MB;
- 分模块阈值:比如某个so库、某个assets文件的大小增量相对上一次不超过500KB。
这样做的意义是:每次提交代码、每次依赖升级,开发者在合入前就能看到"我这行代码让包体涨了1.3MB",而不是等到提测后才发现包体又失控了。
6.2 每次发布前拉取一份包体报告
CI门禁只解决"合入前拦截",但很多体积变化是在长周期的迭代中慢慢积累上去的,单次diff可能看不出问题。所以除了门禁,我建议在每次构建release包时,自动生成一份完整的包体积报告,并归档到固定位置。
报告里至少包含这些内容:
- 整体APK体积和各部分模块占比;
- 文件大小Top 20清单;
- dex中最占空间的Top 20类;
- 与上一个正式版本的体积对比明细。
我实践下来的经验是,这些报告不仅对研发有用,对产品也有说服力。当你拿着"上个版本包体是58MB,这版本增长到61MB,主要原因是XX需求引入了一张2MB的启动图和一个1.2MB的SDK"这样的报告去找产品对齐时,对方会更容易接受"要么砍功能、要么接受体积上涨"的权衡,而不是等到用户安装率下跌后再来一起背锅。
6.3 定期做体积治理checklist
就算门禁和报告都做好了,仍然会有一些漏网之鱼,因为不是所有的体积变化都能通过单次diff暴露出来。比如:一段时间内多个功能各加了几百KB,加起来就是好几MB;又比如图片资源在多次迭代中被反复复制粘贴改大小,慢慢堆出一堆非常接近的变体。
所以我会建议团队每两个季度做一次专门的体积治理checklist检查,内容大致如下:
- 是否还有体积超过1MB的未压缩大图;
- 是否能清理掉lint报出的无用资源;
- 是否有重复的第三方依赖;
- 是否记得在所有release构建里开启shrinkResources;
- assets里是否出现了新的可下载内容被内置进包的苗头;
- 新增SDK时是否都记录过体积增量。
这份checklist也不需要很重,放在团队文档里,每次大版本规划时过一遍就行。真正目的是让"包体积"成为团队的技术文化里被持续盯住的一个指标,而不只是某一次优化的临时KPI。
7. 优化结果复盘与踩坑清单
最后分享一下整个优化战役的最终数据和过程中印象深刻的几个教训,希望能替你省掉一些弯路。
第一版优化完成后,APK从88MB降到51MB(降低约42%),下载传输大小从42MB降到26MB。具体拆解一下:资源优化贡献了14.6MB,so库精简和剔除贡献了11.2MB,代码裁剪和依赖清理贡献了7.4MB,assets瘦身贡献了6.8MB,资源混淆和arsc优化又挤了2.9MB。从改动量和风险控制角度看,资源和assets的优化是最划算的,改动低、风险低、收益大。
值得单独拎出来说的教训有几条:
- 不要迷信"开启minify就万事大吉"。很多项目的ProGuard/R8规则其实是"反裁剪"的,规则写得太宽反而让R8失去作用。建议定期审视keep规则,删掉不再需要的部分。
- 第三方SDK是包体积的最不可控因素。每次集成SDK时,要用空工程跑一遍体积对比,否则它给你带来的增量远超过你的想象。尤其是在某个SDK里通过manifest引用了一堆so和资源的情况下,处理起来最痛。
- 永远留着前一个版本的APK做对照。阶段性的体积优化,一定要保存好上一个版本的包文件和体积报告。否则当你压完一轮,却说不清这25MB是从哪省出来的,后续复盘和向上汇报时都会很被动。
- 资源混淆放最后。它带来的收益在5%到8%之间,但引入后可能因为白名单配置不到位导致运行时资源找不到。所以先把常规手段用尽,再考虑它,性价比才最高。
根据我个人实际维护多个应用的经验,包体积优化最难得不是某一招技能,而是建立"每次改动都要关心体积"的潜意识。包体积不会自己变小,它只会像一个没有盖子的垃圾桶,被一点一点扔进越来越多的东西。希望通过这篇文章里的方法,你的项目也能把这桶垃圾清空一次,并且以后每次有新东西要扔进去之前,都先想一想:它值得占这几十KB甚至几MB吗?