用户一般都不肯意去下载一个比较大的程序,特别是不在 WIFI 的状况下。若是你的安装包很小,用户仍是愿意下载安装体验下的。如今市面上知足某种需求的 App 一般都会有不少款,如何让用户愿意下载你的 App 来体验?安装包越小,在 WIFI 状况下,极速下载安装,开始体验。在移动网络状况下,包体积越小,用户安装的的可能性越大。因此安装包大小对用户的转换率有很大的影响。接下来就和你们分享下我在实际中工做中对包体积优化的一些经验。android
既然是要优化 Android APK 安装文件的大小,首要须要了解下 APK 文件的结构。将 APK 文件拖进 AndroidStudio 能够清楚的看到 APK 文件组成部分。APK 主要由如下几部分组成:git
其实 APK 最核心的就两个内容,图片资源和代码。因此包体积优化主要是从这两方面入手。例如检查 assets
目录下是否有没有用到的资源。通常来讲不多会在 assets 目录放一些没用的资源,主要是集成第三方 SDK (如高德、Baidu地图等)的时候须要放一些资源进去,好比图片、音频文件等。随着项目的迭代,界面 UI 的风格和之前相比发生了很大的变化,那么之前不少图片资源也就不可用了,因此在 res
目录下的可能会存在不少不用的图片,这是咱们清理未使用资源最重要的一个文件夹。除了图片,而后就是 classes.dex
文件 了,通常咱们本身的程序的业务代码不会对包体积产生很大的影响,主要是使用了大量的第三方库,以及集成公司内部其余团队的一些 module ,可能这些 module 包含了大量咱们用不到的代码或者资源。程序员
在优化以前,来看下我所作项目的安装包大小为 73437KB(71.7MB),为后面作的优化好有一个对比,看看具体的优化幅度。github
手动移除资源有两个好处:一个是减小安装包的体积,另外一个是减小源代码的体积。web
在 AndroidStudio 中有两种方式帮咱们找到未使用的资源:算法
Analyze -> Inspect Code,实际上就是经过 lint 工具帮咱们找不用的资源,除了图片资源,还会帮我找到代码中存在的潜在问题,运行效果以下图所示:设计模式
双击 shift,输入 Remove Unused Resources,而后回车。因为上面的方式不只找出未使用到的资源,还会检测代码,因此运行的比较耗时。若是你仅仅只想找出未使用的资源,可使用双击 shift 的方式,它们检测的结果都是同样的。性能优化
上面的工具在使用的过程当中有两个坑:微信
用到的资源,依然报没有引用。如一些 drawable 文件的 xml 资源网络
它还会移除不少布局中的id,若是项目中使用了 ButterKnife,是经过 R2 来应用 id 的,该工具没法检测这种状况
因此,在针对 drawable 目录下的资源咱们能够经过 git 将其 revert,由于咱们的 icon 不多会放进 drawable 目录的。对于布局中声明的 id 被移除,咱们能够将 layout 文件夹 revert。
经过上面的操做,成功将包体积减小了 2.3M:
操做 | 体积 | 减小 |
---|---|---|
优化前 | 73437KB(71.7MB) | - |
Inspect Code | 71054KB(69.3MB) | 2383KB(2.3M) |
在手动移除未使用的资源的过程当中,发现了另外一个问题。如今都是模块化工程了,咱们项目有几十个 module,不少 module 中尽然包含了系统默认的 ic_launcher 图标,新建 module 默认生成的,而咱们项目的图标名字改成了 app_icon
,也就是里面的 ic_launcher 是没有用的。每一个 module 下关于 ic_launcher 就 8 个文件夹:
drawable -> ic_launcher_background.xml drawable-v24 -> ic_launcher_foreground.xml mipmap-anydpi-v26 -> ic_launcher.xml -> ic_launcher_round.xml mipmap-hdpi -> ic_launcher.png -> ic_launcher_round.png mipmap-mdpi -> ic_launcher.png -> ic_launcher_round.png mipmap-xhdpi -> ic_launcher.png -> ic_launcher_round.png mipmap-xxhdpi -> ic_launcher.png -> ic_launcher_round.png mipmap-xxxhdpi -> ic_launcher.png -> ic_launcher_round.png
有的时候,这些 module 可能须要这些 launcher,虽然在发布的时候不须要,可是咱们可能须要单独是运行这个组件,通常会有一个 debug manifest 和 release manifest,而后经过一个标记来判断是 library 仍是 application。其实也能够用过其余方式来实现这种 debug 和 release 的状况(能够在 module 工程外 套一层工程,该工程包含这个 module,做为能够运行的 application)。经过这种方式,module 就不须要存在 application 的状况,也就不须要 launcher 图标了。
其实这也是开发者很是容易忽略的问题,例如,咱们依赖的不少其余部门的内部库,经过 ctrl+shift+r 查找 ic_launcher,会发现不少 aar 会有 launcher 资源。甚至有些不规范的第三方开源库也一样存在这些问题。
操做 | 体积 | 减小 |
---|---|---|
优化前 | 73437KB(71.7MB) | - |
Inspect Code | 71054KB(69.3MB) | 2383KB(2.3M) |
Remove Launcher | 71035KB(69.3MB) | 19KB |
为何移除了这么多的 launcher 图片,为何 apk 的大小只是减小了 19KB?(具体哪些地方减小了,能够经过 Compare with previous APK 功能进行对比)。
因为最终生成 APK 的时候,同名文件只会使用一个资源,也便是只会存在一份,因此优化的幅度不大(关于多个 module 相同路径存在相同文件名,打包时会有优先级,你们能够查看官方文档)。可是清理咱们项目中一些垃圾资源。
其实,在咱们工程的 app/build.gradle 中配置了开启 shrink resource 了:
minifyEnabled true shrinkResources true
咱们使用的程序的图标名字使用的不是 ic_launcher,而是 app_icon,咱们经过 APK Analyze 分析咱们的 APK 发现 ic_launcher 资源还在,ic_launcher 名字的图标上在程序中应该没有被用到,为何没有被 shrink 呢?有两种可能:
咱们先来项目中的 shrink 有没有生效。 我放一个新的资源(abc.webp)到工程中去,而后从新打包,若是该文件被shrink了说明 shrink 是生效的(也就间接说明了程序中某个地方用到了 ic_launcher),若是没有被 shrink 说明上面的配置没有使得 shrink 生效,想办法让其生效便可。
经过 APK Analyze 打开新生成 APK 文件,发现新加入的 abc.webp
文件依然存在:
说明 shrink 没有生效。明明已经配置了 minifyEnabled、shrinkResources,为何没有生效呢。
通过一番查找,原来是在 proguard 文件中设置了不要 shrink :
-dontshrink
把这行注释,而后从新打包,发现减小了 3.37MB
操做 | 体积 | 减小 |
---|---|---|
优化前 | 73437KB(71.7MB) | - |
Inspect Code | 71054KB(69.3MB) | 2383KB(2.3M) |
Remove Launcher | 71035KB(69.3MB) | 19KB |
ShrinkResources | 67576KB(65.9MB) | 3459KB(3.37M) |
shrinkMode 主要有两种:safe、strict,默认模式为 safe。
能够在 res/raw/keep.xml
文件中配置 shrinkMode:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="safe"/>
若是开启了 shrink resource,当 shrinkMode = safe 时,打包的时候会主动寻找那些可能被引用的资源,如经过 resources.getIdentifier() 方式获取资源,该资源不会被缩减,当 shrinkMode = strict 严格模式时该资源不会被缩减。
我在作实验的时候发现,若是一个资源被 shrink 了,它可能还在 APK 中,只不过该资源的体积变得很是小。
若是你将 shrinkMode 设置为 safe,那么可能没有被用到的也被保留了,由于检测可能没有那么精准。
你能够将 shrinkMode 设置为 strict,这个时候须要将经过 resources.getIdentifier(A)方式获取的资源 keep 起来。能够在 keep.xml 中配置要保留的文件:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="strict" tools:keep="@drawable/ic_get_by_identifier"/>
更多关于混淆相关的知识,能够查看 AndroidAll
Android4.0 开始支持 webp,可是只有在 Android4.3 才支持透明度、无损 webp。因此若是你的 app 最低支持 4.3 的话,可使用 webp 代替 png。
在 AndroidStudio 中支持一键转化,能够选择转码的质量比,还能够选择若是转成的 webp 反而比原来的 png 还要大,能够跳过。
操做 | 体积 | 减小 |
---|---|---|
优化前 | 73437KB(71.7MB) | - |
Inspect Code | 71054KB(69.3MB) | 2383KB(2.3M) |
Remove Launcher | 71035KB(69.3MB) | 19KB |
ShrinkResources | 67576KB(65.9MB) | 3459KB(3.37M) |
Png2webp | 64505KB(62.9M) | 3071KB(3M) |
因为以前 R8 还不是很稳定,因此咱们将其关闭了。如今都 AndroidStudio 3.6 了,咱们将其打开:
android.enableR8=true
虽然官网上说 R8 支持现有 ProGuard 规则文件,可是在实际使用的时候仍是会有些问题,解决一些混淆配置上的问题,从新打一个 release 包,发现减小了 0.9M:
操做 | 体积 | 减小 |
---|---|---|
优化前 | 73437KB(71.7MB) | - |
Inspect Code | 71054KB(69.3MB) | 2383KB(2.3M) |
Remove Launcher | 71035KB(69.3MB) | 19KB |
ShrinkResources | 67576KB(65.9MB) | 3459KB(3.37M) |
Png2webp | 64505KB(62.9M) | 3071KB(3M) |
R8 | 63506KB(62M) | 999KB(0.97M) |
上面是 R8 的普通模式,R8 还有彻底模式,还会作一些额外的优化操做,R8 开启彻底模式,可是目前仍是实验性质的:
android.enableR8.fullMode=true
从新打一个 release 包,发现减小了 0.16M:
操做 | 体积 | 减小 |
---|---|---|
优化前 | 73437KB(71.7MB) | - |
Inspect Code | 71054KB(69.3MB) | 2383KB(2.3M) |
Remove Launcher | 71035KB(69.3MB) | 19KB |
ShrinkResources | 67576KB(65.9MB) | 3459KB(3.37M) |
Png2webp | 64505KB(62.9M) | 3071KB(3M) |
R8 | 63506KB(62M) | 999KB(0.97M) |
R8 FullMode | 63333KB(61.8M) | 173KB(0.16M) |
咱们还能够经过自定义 View 来代替一些状态图标,好比订单状态、退款状态等。以下所示:
相似这些图标都是可使用自定义 View 来完成,能够减小大量的图片资源。若是状态不少,就会须要不少的状态图标,若是支持国际化的话,还须要为每一个国家生成对应的状态图标。
通过自定义 View 替换状态图标后,包体积减小了 0.366M:
操做 | 体积 | 减小 |
---|---|---|
优化前 | 73437KB(71.7MB) | - |
Inspect Code | 71054KB(69.3MB) | 2383KB(2.3M) |
Remove Launcher | 71035KB(69.3MB) | 19KB |
ShrinkResources | 67576KB(65.9MB) | 3459KB(3.37M) |
Png2webp | 64505KB(62.9M) | 3071KB(3M) |
R8 | 63506KB(62M) | 999KB(0.97M) |
R8 FullMode | 63333KB(61.8M) | 173KB(0.16M) |
CustomView | 62958KB(61.4M) | 173KB(0.36M) |
微信使用的 AndResGuard 能够对资源资源路径以及资源名字进行混淆,资源名字所有改为相似 abc 的样子。能够大大减小名字字符占用的空间大小。
特别是模块化后,为了防止资源重名,咱们都会在资源的加上模块前缀,这样致使资源的名称就更长了。使用 AndResGuard 的时,程序中经过 getIdentifier 方式获取资源,必定要加入白名单,这个能够在程序中全局查找。
经过 AndResGuard 混淆后,包体积减小了 3.54M:
操做 | 体积 | 减小 |
---|---|---|
优化前 | 73437KB(71.7MB) | - |
Inspect Code | 71054KB(69.3MB) | 2383KB(2.3M) |
Remove Launcher | 71035KB(69.3MB) | 19KB |
ShrinkResources | 67576KB(65.9MB) | 3459KB(3.37M) |
Png2webp | 64505KB(62.9M) | 3071KB(3M) |
R8 | 63506KB(62M) | 999KB(0.97M) |
R8 FullMode | 63333KB(61.8M) | 173KB(0.16M) |
CustomView | 62958KB(61.4M) | 173KB(0.36M) |
AndResGuard | 59323KB(57.9M) | 3635KB(3.54M) |
在主流的手机CPU架构都是 ARM,基本上只要支持这一种架构就能够了。更多关于这方面的知识能够查看 Android NDK ~ 基础入门指南
咱们来看下市面上主流的 app 支付宝和微信的 CPU 架构:
armeabi-v7a 是向下兼容 armeabi,arm64-v8a 能兼容 armeabi-v7a 和 armeabi
咱们项目中也是只支持一种 armeabi-v7a 架构,减小 so 文件体积大小
release { ndk { abiFilters 'armeabi-v7a' } //... }
到此,就介绍完了我此次包体积优化相关内容了,差很少了减小了 20% 的包体积大小。固然优化是无止尽的,除了上面的一些优化手段还有 app Bundles 的方式(须要结合 Google Play 一块儿);还能够考虑经过 BackgroundLibrary 替换程序中大量的 shape、selector 文件,减小包体积,可是该库对性能有必定的影响,因此我尚未使用,后面能够考虑是否还有更好的方案;还能够找出程序中重复的图片(图片内容一致,名字不一样);固然还有插件化,插件也须要瘦身,减小下发消耗的流量。
另外本文涉及到的代码都在个人 AndroidAll GitHub 仓库中。该仓库除了 性能优化
,还有 Android 程序员须要掌握的技术栈,如:程序架构、设计模式、性能优化、数据结构算法、Kotlin、Flutter、NDK,以及经常使用开源框架 Router、RxJava、Glide、LeakCanary、Dagger二、Retrofit、OkHttp、ButterKnife、Router 的原理分析 等,持续更新,欢迎 star。