Android App安装包瘦身计划
Android App安装包瘦身计划
Android App安装包体积优化: 理由, 指标和可以采用的方法.
本文内容归纳如下图:

为什么要安装包瘦身
- 安装包需要瘦身吗?
- 不需要吗?
安装包要瘦身的主要原因就是考虑应用的下载转化率和留存率.
应用太大了, 用户可能就不下载了, 尤其是移动网络或者流量收费的情况下.
再者, 因为手机空间问题, 用户有时候可能需要选择卸载一些应用, 就会先盯上那些占空间大的, 所以应用大小也会也影响留存率.
但现在什么时代了, 到处都是WiFi, 流量包又大又便宜, 网速快到飞起, 手机硬件也在不断升级, 更大更快更高级, 安装包真的还需要瘦身吗? 有人在乎吗?
那当然还是有一定必要的, 不然这篇文章写到这里已经结束了.
因为就算用户不在乎你这个应用有多少大, 决定要下载安装了, 安装包越大, 下载时间就会越长, 没等下载完成就因为各种原因取消或者断网了.
或者因为下载时间稍微有点长, 等完成以后, 用户已经忘了要用这个app了.
现代人的耐心很有限, 不然也不会有小程序这样轻量级的解决方案.
安装包瘦身可以地定量, 在一定程度上改善问题.
除了下载下载转化率和留存率, 安装包体积优化还有一些理由:
- 预装应用的推广成本.
- 满足应用市场的最大包体积限制.
- 在技术甚至业务层面, 我们可以重新审视我们的codebase, 是否需要删除一些低价值的业务, 清理无用的代码和资源, 进行进一步的重构和改善.
安装包的构成
Apk安装包本质上是一个zip压缩文件, 解压之后我们可以看到其中包含的内容, 通常包含:
- 一个或多个
classes.dex文件: 代码编译结果. assets/文件夹.resources.arsc: 编译后的二进制资源文件, 包含了所有res/values中的XML内容, 如字符串, style等, 也包括对没编译进来的资源(比如layout和图片)的路径.res/: 没有被编译进resources.arsc文件的资源: layout, drawable等.AndroidManifest.xml: merge后的manifest文件.META-INF/: 包含签名文件等.
如果有native的库, 可能还会有lib/文件夹.
尺寸指标
安装包大小有几个指标呢?
有三个:
- 文件尺寸: 这可能是最直观的一个, 就是我们build出来的文件大小. 但是其实它是最不重要的一个指标.
- 下载尺寸: 下载app需要的大小, 最重要的指标. 因为下载市场(Google Play)会帮我们做优化, 下载尺寸会比文件的尺寸小.
一般增量安装和全新安装需要的大小是不一样的. - 安装尺寸: app安装到手机上, 启动解压之后的大小, 关系到app的留存率.
安装包分析工具
在做安装包体积优化之前, 建议用工具看一下当前的安装包情形, 找到当前的bottle neck, 这样才能有的放矢.
优化之后也好做一个比较.
APK Analyzer
APK Analyzer是Android Studio自带的工具.
使用APK Analyzer的三种方法:
Build > Analyze APK.., 选择.apk文件.- 切换到Project View, 双击build/outputs/apk目录下的apk文件.
- 把apk文件拖到Android Studio的编辑窗口中.
利用这个工具可以查看apk的各个组成部分以及它们各自的大小, 包括文件大小和预估的下载大小.
这个工具还可以比较两个APK, 从而可以看出具体是哪个部分有尺寸的变化.
官方插件: Android Size Analyzer
在Android Studio中可以添加Plugin: Android Size Analyzer.
安装之后重启Android Studio, 接着就可以点击菜单Analyze > Analyze App Size.
点击之后会给出建议, 显示出项目中比较大的文件, 建议转化一些图片到webp格式, 建议开启代码压缩混淆等.
安装包瘦身措施: Coding阶段
减少代码(native和Java代码)
- 控制第三方库的使用: 依赖第三方库的时候, 尽量减小范围, 或者使用更加友好的替代. (ProGuard会移除不用的代码, 但是它不会移除库的内部依赖.)
- 减少不必要的生成代码.
- 减少枚举的使用.
- 减小native库: 移除debug symbols和关闭so库压缩.
控制资源使用
要控制资源的使用: 减少资源的数量, 减小资源的大小.
设备屏幕支持
Android有很多屏幕密度类型:
ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi and xxxhdpi
并不是需要每个资源都提供多密度版本的.
当一个屏幕密度下没有提供特定的资源, Android会自动根据这个资源其他密度的版本进行缩放.
如果你的app仅需要提供缩放图像, 你可以只在drawable-nodpi中提供单个资源. (建议还是至少有一个xxhdpi.)
shape的使用
很多时候我们并不需要一个图片, 比如纯色或渐变色的背景, 带边框, 带圆角等.
用<shape>尺寸就会比较小, 也不用多个密度的版本.
复用资源
这里的复用可以是改变了颜色和旋转方向等的复用.
比如改资源的tint, 或者把一个图旋转了之后再用. (thumb up变成thumb down了).
压缩图片
可以用一些工具对PNG和JPEG图片进行无损压缩: 图片质量无损但是尺寸变小.
使用WebP格式的图片
可以使用WebP格式的图片, 比JPEG和PNG压缩得更好.
Android Studio提供了转换工具: Create WebP images
注意: Google Play只接受PNG作为launcher icon.
使用矢量图
可以使用矢量图来作为可伸缩的资源, 在Android中是VectorDrawable对象.
但是矢量图的渲染需要系统的时间花销, 所以推荐只有小的icon使用矢量图.
逐帧动画
不要再使用很多个图片逐帧播放来实现一个动画效果了.
尽量用改变属性的动画来节省资源使用.
另外还有一些手段比如在程序中绘制, 而不是使用图片.
安装包瘦身措施: Post-coding阶段
Lint静态分析
Lint是静态分析工具, 会提示项目中的各种问题.
针对不用的资源, 可以专门这样检查:
在Android Studio中: Analyze -> Run Inspection by Name... -> Unused Resources.
扫描后会对所有未使用的资源给出警告.
注意, 因为是Lint静态检查, 所以会有一些错报和漏报的情况:
assets/目录不被扫描.- 检测不到第三方库中带来的不用的资源.
- 错报: 资源在项目中实际上用了, 但是被检测出来了. 如果资源是被动态引用的, 比如用
getIdntifer()方法, 拼名字使用, 会被lint错误地报告说没有用到. - 漏报: 实际上没有用到, 但是没有检测出来. 这是因为还有另一个没有用到的代码引用了这个资源.
比如有一个没有人用的Fragment, 它的布局文件就不会被检测出来, 因为写在了代码里. (注意: 如果是不用的布局中include了另一个布局, 这两个布局都能被检测出来.)
静态检查只是帮我们检测并报告, 真正要移除这些资源还得靠我们手动删除.
代码压缩和混淆
ProGuard -> R8会帮我们删除不用的代码, 进行名称改写和代码优化.
minifyEnabled true
不用的代码被移除了, 类和成员的名称都被改得很短, 从而有效地减小了dex文件的大小.
这里需要注意rules的编写, 不要过度保护.
比较追求极致的方法会从这个方面下手, 进一步细化优化ProGuard Rules, 以达到更深度的混淆和压缩.
shrinkResources
移除不用资源的一个有利工具shrinkResources:
android {
// Other settings
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
为了开启压缩资源, 必须先开启压缩代码, 即minifyEnabled要为true.
这样, 在打包的时候, 首先移除了不用的代码, 然后Gradle才能检测哪些资源还在被代码引用, 从而移除不用的资源.
注意这个过程并没有真的删除掉资源, 只是将不用的资源替换了一个简单版本.
跟代码压缩一样, 资源压缩也可以指定一个自定义的规则, 明确指明哪些资源要保留.
注意开启了代码压缩和混淆之后, 编译速度会变慢, 所以一般debug版本不开启.
这种删除方式靠谱吗? 精确吗? 会误删吗?
常规情况下resource shrinker还挺准确的.
但是如果你的代码(或依赖的库)中有用到Resources.getIdentifier()方法来获取资源, (比如AppCompat中这么做), 那么说明你的代码会根据一个动态生成的字符串来查找资源.
如:
val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)
这个时候resource shrinker默认会表现得很保守, 会保留所有符合这个名字模式的资源, 认为它们都是可能会被用到的, 从而不会删除它们.
而且resource shrinker还会扫描字符串, 查找符合资源格式的URLs, 比如file:///android_res/drawable//ic_plus_anim_016.png, 从而保留这些对应的资源.
所以不用担心, 默认情况下, 资源压缩都是"better safe than sorry"的模式.
如果你想激进一点, 可以在keep.xml里面把压缩模式指定为shrinkMode="strict", 这时候你就必须手动指定需要keep的动态使用的资源了.
如何查看都删了什么呢?
用命令行:
./gradlew clean assembleRelease --info | grep "Skipped unused resource"
注意这里assembleRelease可以替换成任何一个开启了资源压缩的type.
执行了之后会列出所有被替换成dummy file的资源.
用IDE:
在Android Studio的Build窗口下, Toggle View切换到详细信息模式下. Build, 然后查找信息, 可以看见信息中会有一句:
Removed unused resources: Binary resource data reduced from XXXKB to YYYKB: Removed Z%
显示资源压缩节省出来的大小.
文件:
在app的输出路径(<module-name>/build/outputs/mapping/release/)下, 和mapping.txt一起, 还有一个resources.txt文件.
文件的最后会列出一系列的Skipped unused resource.
同时这个文件里也详细地写了资源之所以会被保留下来的理由. 关键字: matches string pool. 这是前面说过的为了防止有动态使用资源的情况而采取的保守措施.
如果有空的话可以把这些Skipped unused resource标注的文件都手动从代码库里删除.
声明支持的配置, 删掉其他
Gradle的resource shrinker会删除没有被代码用到的资源, 但是不会删掉不同设备配置下的可替代资源
(alternative resources).
可以利用resConfigs, 来删除你的app并不需要的配置资源.
原理就是声明app支持的配置, 这样其他不支持的配置的资源就会被删除, 从而减少apk的尺寸.
比如你包含了一个库, 这个库中带有各种语言的资源, 这样默认你的app就会包含所有这些库中包含的语言的字符串. 你可以明确声明app支持的语言, 那么其他语言资源就会被删除:
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
见Remove unused alternative resources
安装包瘦身措施: 发布阶段
因为有的资源会提供多个版本, 所以一个用户下载的apk中可能包含了一些他永远也用不到的内容.
为了保证每个用户的下载尺寸都尽量小, 我们可以改进发布包的形式: 拆分成多个针对不同机型的apks;
如果是在Google Play发布, 还可以用App Bundle, 由Google Play帮我们做拆分.
Build多个apks
根据屏幕密度和指令集(ABI)的不同, 可以拆分多个apk, 每种配置只下载自己需要的资源, 从而达到减小安装包大小的目的.
Gradle就可以帮我们做这个拆分创建的工作.
具体实现见:
Build multiple APKs.
Android App Bundles
用Google Play发布应用时可以使用Android App Bundle的格式, 这是一种新的发布格式, 包含了应用所有的编译代码和资源, 把apk的生成和签名留给Google Play来做.
Google Play会根据用户不同的设备配置来生成优化的apk, 这样用户下载的就只是对应自己设备的代码和资源. 开发者不用为不同设备准备多个apk, 用户也得到了更小更优化的结果.
安装包瘦身措施: 进阶补充
要更加极致地压缩安装包, 还有一些更加复杂的手段, 比如:
- 业务层面: 动态feature, 资源动态加载.
- Dex: 加大混淆力度, 混淆四大组件和View; 去除debug信息; 优化Dex分包; Dex压缩.
- native库: 去除debug信息; 压缩; 剪裁.
- 资源: 资源名混淆成短路径; 压缩.
另外, 还可以将对安装包体积的分析加入到CI中, 进行自动化监控, 这样可以得到各个版本的尺寸变化, 及时获知是否不经意引入大的依赖, 引入过大的资源等情况.
总结
总结回顾一下本文内容, 对于安装包瘦身的话题:
首先, 讨论了我们为什么要做这个事情 -> 为了增加app被下载安装使用和留存的几率. (为什么要瘦身呢? -> 为了提升自己增加竞争力.)
其次, 介绍了安装包的构成, 相关的指标和用于检测的工具. (瘦身需要关注的指标和测量方式.)
重点介绍, 如何减少安装包体积的实践方法. (瘦身的各种手段.)
参考资料
- Reduce your app size
- Shrink, obfuscate, and optimize your app
- Configure APK Splits
- Build multiple APKs
- Screen sizes and densities
- Analyze your build with APK Analyzer
- 支付宝 App 构建优化解析:Android 包大小极致压缩
- 美团 - Android App包瘦身优化实践
- 极客时间: Android开发高手课, 包体积优化 (上), (下)
最后, 欢迎关注微信公众号: 圣骑士Wind

Android App安装包瘦身计划的更多相关文章
- Android APK安装包瘦身[转]
很显然,APK安装包越小越好.下面从代码,资源文件,使用策略几个方面简要介绍下: 代码 保持良好的编程习惯,不要重复或者不用的代码,谨慎添加libs,移除使用不到的libs. 使用proguard混淆 ...
- iOS 安装包瘦身(下篇)
本文来自网易云社区 作者:饶梦云 2.4. 清理无用代码 2.4.1. Dead Code Stripping Activating this setting causes the -dead_str ...
- iOS安装包瘦身的那些事儿
在我们提交安装包到App Store的时候,如果安装包过大,有可能会收到类似如下内容的一封邮件: 收到这封邮件的时候,意味着安装包在App Store上下载的时候,有的设备下载的安装包大小会超过100 ...
- iOS 安装包瘦身 (上篇)
本文来自网易云社区 作者:饶梦云 1. 安装包组成 谈到 App 瘦身,最直接的想法莫过于分析一个安装包内部结构,了解其每一部分的来源.解压一个 ipa 包,拿到其 payload 中 app 文件的 ...
- 专项测试——移动app安装包检测
一.背景和现状 安装包的重要性无需多提,针对安装包质量控制越来越严格和规范,包括证书.文件大小.安装成功率等,APP的证书及混淆是影响APP的安装成功率及代码安全性的很大因素,随着功能迭代,安装包也会 ...
- 关于iOS和Android的安装包更新笔记
关于iOS和Android的安装包更新问题 1. Android更新apk 1)使用DownloadManager下载 2)使用HttpClient下载 apk的下载不能使用ssl,即不能使用http ...
- IIS放置的APP安装包在浏览器无法打开
无法打开的提示 操作步骤 1.将APP安装包放置到指定的文件夹中. 2.在IIS中MIME中添加MIME类型 扩展名:.apk MIME类型:application/vnd.android.pac ...
- iOS UIViewController的瘦身计划
代码的组织结构,以及为何要这样写. 那些场景适合使用子控制器,那些场景应该避免使用子控制器? 分离UITableView的数据源和UITableViewDataSource协议. MVVM的重点是Vi ...
- 【Android端APP 安装包检查】安装包检查具体内容及实现方法
一.安装包检查的具体包含内容有哪些? 1.安装包检查的一般内容包括: 安装包基本信息检查: 文件大小: xx MB 包名: com.xx 名称: xx 本次安装包证书与外网证书对比一致性:是 版本号 ...
随机推荐
- 浅谈网络爬虫爬js动态加载网页(一)
由于别的项目组在做舆情的预言项目,我手头正好没有什么项目,突然心血来潮想研究一下爬虫.分析的简单原型.网上查查这方面的资料还真是多,眼睛都看花了.搜了搜对于我这种新手来说,想做一个简单的爬虫程序,所以 ...
- 程序代写, CS代写, 代码代写, CS编程代写, java代写, python代写, c++/c代写, R代写, 算法代写, web代写
互联网一线工程师程序代写 微信联系 当天完成 查看大牛简介特色: 学霸代写,按时交付,保证原创,7*24在线服务,可加急.用心代写/辅导/帮助客户CS作业. 客户反馈与评价 服务质量:保证honor ...
- Metasploit渗透测试
原创博客,转载请注出处! 学习笔记 参考书籍<Metasploit渗透测试指南(修订版)> 经过多日学习,初步掌握metasploit基本参数和使用方法,现进行渗透测试实践 靶机IP:16 ...
- 原子操作CAS-最小的线程安全
原文连接:(http://www.studyshare.cn/blog-front/blog/details/1166/0 )一.原子操作是什么? 如果有两个线程分别执行两个操作A和B,从第一个线程执 ...
- Hadoop 学习之路(二)—— 集群资源管理器 YARN
一.hadoop yarn 简介 Apache YARN (Yet Another Resource Negotiator) 是hadoop 2.0 引入的集群资源管理系统.用户可以将各种服务框架部署 ...
- 简单的scrapy实例
前天实验室的学长要求写一个简单的scrapy工程出来,之前也多少看了点scrapy的知识,但始终没有太明白,刚好趁着这个机会,加深一下对scrapy工作流程的理解.由于临近期末,很多作业要做(其实.. ...
- fis3前端工程构建配置入门教程
一.前言 fis3是百度推出的一款前端工程构建工具,类似的还有webpack,gulp等工具:无论大家有没有使用过,从事前端行业应该都略知一二了,所以对于此类工具用干嘛的我这里就不做重复了. 其实对于 ...
- mac 添加new file.md
1. 打开mac自带的"Automator",文稿类型选择应用程序:   2. 选择:实用工具 -> 运行 AppleScript  3. 黏贴如下代码到上图的右侧,c ...
- C# 6 新语法
1. using 声明的静态用法 2. 表达式体方法 3. 表达式体属性 4. 自动实现的属性初始化器 5. 只读的自动属性 6. nameof 运算符 7. 空值传播运算符 8. 字符串插值 9. ...
- 基于C#的机器学习--我应该接受这份工作吗-使用决策树
决策树 要使决策树完整而有效,它必须包含所有的可能性.事件序列也必须提供,并且是互斥的,这意味着如果一个事件发生,另一个就不能发生. 决策树是监督机器学习的一种形式,因为我们必须解释输入和输出应该是什 ...