深入理解 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 技巧

8146 次点击
所在节点    Android
40 条回复
geekvcn
2021-10-23 22:20:26 +08:00
毫无意义,一个技术过硬的码农,可以完全用 NDK 开发一个纯原生,甚至图标界面全都用代码矢量绘制,完事一个体积小巧性能飞快的 APP 诞生了,然后呢?

然后淘宝 微信 QQ 等一众垃圾代码写的软件还内置个垃圾 webview 把省下的空间一秒钟占掉,内存也占掉,这个技术过硬的码农的软件被系统后台强杀了,流氓软件在相互唤醒保活和其他非同阵营流氓软件打架。

不说用 NDK 开发,安卓现在能用 JAVA Kotlin 写原生软件的都不多了,webview 套壳多方便,热更新,前端随便招,软件 H5 部分全平台通用,JS 再慢又如何。
zpxshl
2021-10-23 22:39:46 +08:00
@geekvcn 张口就来。
1 主流 app 的包体积控制一直是 kpi ,包体积和转换率有正相关关系,多家实验做过了。
2 内置 webview 在国内基本是必须的,做过的都知道国内手机 webview 的坑有多少,不同版本,各家魔改。
3 楼主提包体积,你提保活,搭不上边吧。
3 js 再慢又如何???
不是所有 app 都是微信那种它做得再差用户都得用的。
Cheons
2021-10-23 23:07:28 +08:00
国内手机厂商坐一块把内置的 webview 标准给统一了,最简单也是最难的一步
yuhuazhu
2021-10-23 23:12:31 +08:00
真羡慕 iOS 的 webview ,Android 不用第三方的一大堆问题要搞 T^T
makelove
2021-10-23 23:48:11 +08:00
这个叫内存,那真的内存叫什么

我对包体积不太在意,但很在意内存用量,象淘宝这种一打开就要用 1.6G 的 App,不知道他们内部会用低端一点的 4G 内存手机测试吗,简直没法用
little2song
2021-10-24 00:09:24 +08:00
@makelove 文章有提到 [我们约定 手机运存 = 电脑内存,手机内存 = 电脑硬盘]
little2song
2021-10-24 00:15:26 +08:00
@geekvcn 大哥,不要这么极端,另外,我之前参与了手 Q 开发,也使用过上面的一些方法来减小体积,纯原生的 apk 价值不大,可以做个小组件,或者工具类 App , 对于亿级流量的 app ,webview 是必须的,但是,也不至于全是 webview 套壳,事实上,手 Q 里面 Webview 的代码占比很小
mxalbert1996
2021-10-24 00:19:43 +08:00
你对 R8 的理解是错的。R8 跟 ProGuard 一样都是 Code Shrinker ,功能也和 ProGuard 一样包括压缩混淆优化,是 Google 开发的 ProGuard 替代品。从 AGP 3.4.0 开始 R8 就是默认的 Shrinker ,如果不显式制定的话是不会用 ProGuard 的(也没有必要用,R8 比 ProGuard 更强大,能进行的优化更多)。
little2song
2021-10-24 00:35:03 +08:00
@mxalbert1996 受教了,是我想当然了,还得多学习
geekvcn
2021-10-24 00:54:47 +08:00
@zpxshl 别特么给自己找理由了,还 KPI ,国内手游明明可以做到启动器数据包分离,这样游戏安装包几十兆就能搞定,但是都内置数据包,完事解压运行双倍空间占用,有 KPI 脑子有坑才这样做。
AX5N
2021-10-24 01:12:31 +08:00
@makelove 真的内存就叫“真的内存”
codehz
2021-10-24 08:13:50 +08:00
@geekvcn 还是国内生态的问题,没有统一商店分发,所有东西最好打包到一起才能“方便”地交给用户(运行时下载的体验实在不好),因此最好是由应用商店统一分发。不同厂商提供商店的差异很大,很多根本不支持单独分发 obb 。。。
Caan07
2021-10-24 08:46:10 +08:00
逆天了,我竟然不知道 iphone13p 最大内存放大到了 1T ?如果 1T 内存那 13p 的价格真的划算到极点。
Jabin
2021-10-24 09:17:18 +08:00
基本同意 @geekvcn
瘦身文章一大堆, 关键还是
1. 混淆
2. 减少资源图片, 换用矢量图
至于使用 webview, 就没啥可聊的了, 把 app 当浏览器使用没啥好说的
模块 /插件化的东西不能完全算作减小 apk 体积大小的方法, 那根不不算一个完整的 apk, 用户更新安装也需要时间, 插件也占用内存, 和瘦身一起聊意思变味了
zpxshl
2021-10-24 09:23:39 +08:00
@geekvcn 你世界观里是不是非黑即白。kpi 的意思是指标,不是不顾一切的指标,一个项目里面多少指标只考虑一个吗?
王者荣耀新安装启动要下载 3g 的数据包体验很好??? 另外下载下来的数据包就不占空间吗? 下载的带宽成本呢?
没搞过移动端你就别回复了。 作为用户你喷产品随便喷,这帖子好好讨论技术就别瞎杠了。
zpxshl
2021-10-24 09:25:29 +08:00
还有一点楼主没提到,通过混淆删 kotlin 的部分生成代码,比如判空,实现简单收益良好
zpxshl
2021-10-24 09:29:39 +08:00
@Jabin 反驳插件的看法。
1 apk 的体积对用户下载留存率有正相关关系。
2 插件一般用于非核心功能,没下载也大概率不影响用户使用。举个例子,我们内置的 webview 内核就是动态下发的,下发成功前降级使用系统的 webview 内核。
这里包体积其实分两种,安装包的体积和安装后的体积,一般关注前者,说白了市场上大部分用户关注啥,产品就关注啥,哪个技术优化带来留存提高都是有实验的。
NewYear
2021-10-24 09:33:31 +08:00
路过,我家服务器也才 128G 内存,你家一部手机就 1T 内存了,您知道每增加 1G 内存就会增加多少耗电量么? 1T 内存就你那小身板的电池能扛得住?还 1T 内存,就你最离谱,讲的是专业的科普,说的却是基本名词都错。
v2yllhwa
2021-10-24 09:49:55 +08:00
@NewYear [我们约定 手机运存 = 电脑内存,手机内存 = 电脑硬盘]
我觉得没必要争论这个名词,尤其是在楼主已经约定了的情况下...
loukky
2021-10-24 10:10:13 +08:00
其实不用约定,至少在塞班时代,手机运存真就等于电脑内存,手机内存真就等于电脑硬盘,前面的人真是少见多怪。

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

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

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

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

© 2021 V2EX