深入理解 android 包体积优化,给 apk 瘦身全部技巧

2021-10-23 21:52:14 +08:00
 little2song

前言

随着 iphone13p 最大内存放大到了 1T ,大内存手机的时代悄然降临,在 android 里面,三星也有,罗老师几年前说:如果我告诉你们我们在做 1T 的手机,你们可能以为我疯了

看看现在,估计未来会有更多手机有 1T 版,大家开始真香了。

但是,如果现在有人说:要做一个 1T 大小的 app ,那他可能是真疯了,至少未来十年不可能。因为手机内存是越大越好,你一个 app 当然是能小就小呀

Android app 的文件格式为 apk ,本文就是探讨对于一个 android apk ,有哪些方法可以减小体积

Apk 组成

要想减小体积,首先我们需要了解 apk 的构成

上面是抽象的 apk 结构,下面我们看一个实际的

将 qq.apk 拖入 android studio

可以看到最大的 R 文件夹,点进去,都是一些图片,第二大的是 assets ,里面是一些表情包以及插件图片

其他的我们刚刚也说过,值得注意的是,里面多了一个 META-INF

他存放了应用的签名信息,其中

这样子,一个 app 安装在手机时,解密这一数字摘要,然后与内部的.MF 文件比对,如果相符,证明资源内容没有被修改

Dex 文件

在 APK 组成中我们可以看到,占用内存最大的是 res ,assets 与 classs.dex 文件,这也是我们的优化方向,接下来,我们看看如何优化 dex

首先我们看看 dex 的结构

更详细的版本在官网,这里如果对这些结构的作用有兴趣,可以看下图的详细版本

ProGuadrd

dex 是代码编译而来,而对于代码文件,最重要的优化就是混淆了,将方法名,属性名等变为又短又无意义的名字,不仅能缩小体积还能避免反编译被人破解

在 IDE 中,我们可以看到 qq 里面的类都是小写字母,里面的变量和方法都按字母顺序排列了,从 a 开始

除了修改变量名,ProGuadrd 还可以在功能等价的基础上重写代码,比如把多个函数调用写到一个函数里面去,更加增大了阅读理解难度(虽然初学者一般已经这样做了),以及打乱格式,增加空格等

主要步骤如下

D8 与 R8 优化

这两平时接触不多,他们主要是在字节码处做优化的,开发时感知不强(感觉就是用来面试的)

D8 主要是在编译字节码时重排序,将占用空间变得更小,比如对于 greetingType 方法,正常编译后的结果是

[000584] Main.greetingType:(LGreeting;)Ljava/lang/String;
0000: sget-object v0, LMain$1;.$SwitchMap$Greeting:[I
0002: invoke-virtual {v2}, LGreeting;.ordinal:()I
0005: move-result v1
0006: aget v0, v0, v1
0008: packed-switch v0, 00000017  // 这里

如果使用 D8 优化,编译后的结果

[0005f0] Main.greetingType:(LGreeting;)Ljava/lang/String;
0000: sget-object v0, LMain$1;.$SwitchMap$Greeting:[I
0002: invoke-virtual {v1}, LGreeting;.ordinal:()I
0005: move-result v1
0006: aget v0, v0, v1
-0008: packed-switch v0, 00000017  //  这里
+0008: const/4 v1, #int 1
+0009: if-eq v0, v1, 0014
+000b: const/4 v1, #int 2
+000c: if-eq v0, v1, 0017

可以看到 0008 处后的几条指令有变化,多了几个 if ,对于不同的 case 做创建不同的变量,可以节省空间

R8 也类似,只是策略有些不一样

更详细的了解可以参考 D8 Optimizations

总之,他们的作用是就是,在不改变功能的情况下,重写部分 class 指令,减小空间占用,但是有可能会增加指令数量

Redex 优化

Redex 是 Facebook 推出的一个优化 Dex 文件的工具,和 D8R8 一样,也是对字节码的处理,有以下效果

  1. 内联函数,减少调用
  2. 删除无用代码
  3. 将只有一个实现类的接口或者父类用实现类代替
  4. 字符串混淆所见

……

不过这个我没用过,但是感觉 Proguard 与 D8R8 都多多少少能做到,可能是他在细节上用了更好的算法

但是不管多少框架,对 dex 文件的优化说来说去也就这些

移除多余的库与代码

最后是移除第三方库和冗余代码,属于业务逻辑上的原因

资源清理

上面都是在代码层面减小 dex ,apk 的另一个空间占用大户,是资源,尤其是其中的图片,

图片,你可知道,多少 OOM 因你而起?多少 app 因你闪退?

图片压缩与更换格式

我们先看看图片为什么那么大

图片的显示,有 ARGB 4 个通道,其中默认的显示模式是 ARGB8888 ,ARGB8888 表示每个通道的颜色区间为[0,255],也就是两个 16 进制数表示,也就是 8bit -> 1 字节

所以 ARGB8888 模式下,一个像素 4 个通道下占用 4 字节,一张 1024*1024 的手机图片图片,就是 $$ 2^{10} * 2^{10} * 2^2 = 2^{22} = 4M $$ 一张图 4M ,太离谱了!

上面是打开后在运存的占用,我们可以修改颜色通道,不然 ARGB565 来减小单个像素所占用运存,不过有点跑题,本篇我们讲的是 app 的大小,也就是所占用手机的内存(我们约定 手机运存 = 电脑内存,手机内存 = 电脑硬盘)

内存与运存中的图片存在形式是不一样的,压缩方法也不一样,很多人容易弄混

回到内存,内存中,图片是以 png ,jpg 等格式存储

我之前开发的时候都是先将 png 图片,往 tinypng 网站中压缩一下再放入,所以可以压缩图片,一般能压个三分之一~三分之二。

也可以更换图片格式,比如 webp ,svg 可以更小,android studio 也提供了对应的支持,但是没有最好的格式,只是适用场景不同

这里多提一下 webp ,因为这是 google 推出的,大家在谷歌浏览器下载图片的时候,一般默认下载下来就是 webp 格式,所谓更小的内存占用,本质上是对图片进行了压缩,webp 的压缩算法是 VP8 视频编码,核心逻辑就是将图片分割成更小的子块,然后预测周围像素值,预测越准,周围的像素值就可以删去,再在图片打开时算出删掉的像素

图片网络化

在微信或者 qq 聊天中,对方发来一张图片,我们在聊天窗口往往先看到一张很模糊的缩略图,当点击时才会加载出高清图,

这个思路也可以用在 apk 中,很多入口较深的高清大图,或者需要经常更新的图片,也许用户根本不看,就没有必要内置在 apk 中,看时加载即可,如果需要提前占位置,可以用缩略图代替

至于哪些图网络化,需要根据业务与用户体验来权衡了

比如淘宝,在断网情况下打开时,只有 icon 内置了

其他策略

无论是对 Dex 还是对资源进行优化,虽然安全有效,但是本质上是将原来有的东西变得更小,对 apk 的瘦身程度是有限的,还有一些”七伤拳“,优化率极高,但是对 apk 的影响也很大,需要谨慎使用。

插件化

所谓插件化,就是将 apk 中的非主要功能弄成独立的 apk ,原主 apk 称为宿主。

比如支付宝里面,就是搞支付的,那么他里面的什么口碑,基金,天猫一堆乱七八糟,同时功能独立的东西就非常适合做成插件,用户用到的时候再从网络加载进来,这样极大的减少了 apk 占用。

但是这里涉及到比较多的技术问题:

  1. 用户现在只有宿主 apk ,如何让宿主加载到插件 apk 里面的代码?
  2. android 四大组件都需要到 manifest 中注册,插件里面的组件显然不可能提前注册到宿主的 manifest 中(不然注册了,插件没加载进来,会找不到类),所以如何让系统认为下载下来的插件有注册?
  3. 宿主与插件资源能否正确互相引用?

一般来说,通过的是代理和反射来处理,腾讯有一个 shadow 框架可以大致实现”零反射“,

不过插件化技术不在今天的讨论范围,有兴趣可以研究下tencent-shadow

当使用了插件化后,项目基本是要重构了,相比起改改 Dex 和图片,这个工程量极大,但是收益也会很高

webview

这里类似于图片网络化,相对于图片,直接将整个界面都变成 url ,

我们手机 app 中的小程序一般都是 url 显示在 webview 中

相关技术可以使用 jsBridge 与 Hybird ,本质上就是通过 bridge 连接 h5 与 android iOS ,实现通信

不过代价就是,加载速度慢于原生,还要注意防止网址篡改等

小结

本文我们讨论的是 apk 的瘦身方案,首先先明确了 apk 的主要组成部分为 dex 文件与资源文件

除了这些常规操作,我们还可以使用插件化与 Webview 方法极致减少体积,但是这两个技术工程量大,而且有性能代价,需要谨慎使用。

参考资料

深入探索 Android 包体积优化(匠心制作-上)

Android 项目中资源文件 -- asset 目录和 res 目录

顶象 App 加固技术解析:DEX 文件格式的详解

D8 Optimizations

Android 开发应该掌握的 Proguard 技巧

8570 次点击
所在节点    Android
40 条回复
jemyzhang
2021-10-24 10:11:10 +08:00
不应该是手机内存=电脑内存,手机存储=电脑硬盘?哪里来的运存这个词
jemyzhang
2021-10-24 10:12:03 +08:00
英文 storage & memory
loukky
2021-10-24 10:14:11 +08:00
@jemyzhang 运行内存的简称
so898
2021-10-24 11:39:31 +08:00
咋说呢,拼多多之前这样一套东西可能还有取舍,还有较为合理的 KPI 制定
拼多多之后,开场一个 H5 ,所有 Native 组件都后面插件化下载装载,管他核心功能非核心功能……
我们老大说过一句很经典的话:当你的应用做到拼多多那样 1M 不到的时候,用户不小心点了一个广告,还没来得及反悔,应用就下完了
NewYear
2021-10-24 11:53:54 +08:00
@v2yllhwa
@loukky

我也勉强作为一个开发者,我是真的不明白,为什么要把同一个东西搞两个名字,而不同的两个东西却使用同一个名称。

普通人这不是更容易弄混吗?一个东西一个名称让普通人更加好记不是么?

毕竟现在大家都有电脑和手机。两个东西都会遇到,交流的时候也会用到啊。
AlexPUBLIC
2021-10-24 13:10:35 +08:00
其实压缩的奥义只有一个:一个聊天工具就干聊天的活就好,非要整什么看点,视频号,钱包;一个支付工具就做支付好了,不要总想着做社交,断舍离,组件少了,自然体积就下来了,啥都想干,去定制 OS 吧,比如 hm
Lemeng
2021-10-24 14:35:13 +08:00
太用心了,有点长
jiayong2793
2021-10-24 16:32:02 +08:00
老板、领导:这不是没事找事吗?新功能写好了没?
muzuiget
2021-10-24 16:56:16 +08:00
写这种文章的“内存”只是指 storage 而不是 memory ,就不想看了。
cev2
2021-10-24 19:21:28 +08:00
下面楼都歪了,→_→我还是习惯手机上 RAM=内存,ROM=闪存的叫法
bclerdx
2021-10-24 20:40:09 +08:00
@Jabin 把 app 当浏览器是不对的吧,app 应该调用外置浏览器吧。
bclerdx
2021-10-24 20:43:15 +08:00
@AlexPUBLIC 不搞点啥,怎么创收呢。
little2song
2021-10-24 22:03:25 +08:00
@muzuiget 不知道你哪来的优越感,我分享文章不是给您过目的
sw926
2021-10-24 23:04:33 +08:00
优化包体积是一个“伪技术”,重复代码和重复资源太多,再怎么优化也没用,关键还是要编码的时候注意,代码要封装,资源要尽量复用。
diaosi
2021-10-25 00:25:31 +08:00
@little2song #6 不是手机开发,看到你一楼描述第一反应仍然是惊讶 iPhone 内存居然到 1T 了,再转头一想觉得怎么都不可能,才意识到你说的是存储空间。所以我们为什么要改变约定已久的称呼去重新约定呢?
viosonlee114
2021-10-25 09:15:05 +08:00
以前的手机可以插存储卡,所以有内置存储和外置存储的说法,所以很多人口中的手机内存就是指的手机内置存储。还有,有必要在这个点上批判楼主吗?非要杠精上身把愿意分享的人都轰走然后贴吧化?
junyee
2021-10-25 09:55:16 +08:00
微信从早期的 几 MB 到现在 230MB 了.

随便一个抓 APP,就有可能有 ffmpeg,webview ,合着全国人民都用着旗舰手机呢.
不仅 dex 巨大, 内嵌资源还多. 多就算了,不常用的资源广告也往里塞,一联网就更新资源.
让我们看广告也不替我们淘流量费.

APP 版本恨不得一天一更新似的.
lvsecoto
2021-10-25 10:02:10 +08:00
aab
Lxcm
2021-10-25 10:13:53 +08:00
纠结运存 内存的都是 00 后吗? 80 90 后的都经历过手机的变革,这些名词都是时代的印证,而且楼主还约定了,很好理解。那些纠结症患者是脑子转不过来吗?总是塞住不好。
john6lq
2021-10-25 12:00:42 +08:00
说白了还是技术的话语权没有产品的大,人家能吹牛,带来流量、拉来投资。
所以说同一个人的开源项目基本可以秒杀他参与的商业项目。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/810075

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX