支付宝 App 构建优化解析:Android 包大小极致压缩

前言

本章节咱们将围绕《支付宝 App 构建优化解析》另启新系列,细分拆解客户端在“代码管理”、“证书管理”、“版本管理”、“构建打包”等维度的具体实现方案展开讨论,带领你们进一步了解支付宝在 App 构建模块下的持续优化。java

本节将主要记录经过对支付宝 Android 包大小进行压缩,来改善运行效率和质量。android

背景

包大小的重要性已经不须要多说,包大小直接影响用户的下载,留存,还有部分厂商预装强制要求必须小于必定的值。可是随着业务的迭代开发,应用会愈来愈大,安装包会不停的膨胀,因此包大小缩减是一个长期的治理过程。web

方案

支付宝也一直在优化包大小的方向上努力,咱们引入了不少方案。 好比:proguard 代码混淆,图片从 png 到 tinypng 到 webp,引入 7zip 压缩方案等。 本方案是有别于上面这些常规的方案,是经过直接删 dex 中的无用信息,达到支付宝包大小瞬间减少 2.1M 的目的,而且不影响整个的运行逻辑和性能,甚至还能下降一点运行内存。数组

方案介绍

  • 引言

在讲详细方案前得稍微说说整个 Java 系的调试逻辑。 JVM 运行时加载的是 .class 文件,Android 为了使包大小更紧凑,而且运行更高效发明了 dalvik 和 art 虚拟机,两种虚拟机运行的都是 .dex 文件(固然 art 虚拟机还能够同时运行 oat 文件,不在本文章讨论范围)。 因此 dex 文件里面信息的内容和 class 文件包含的信息是彻底一致的,不一样的是 dex 文件对 class 中的信息作了去重,一个 dex 包含了不少的 class 文件,而且在结构上有比较大的差别,class 是流式的结构,dex 是分区结构,各个区块间经过 offset 索引。后面就只提 dex 的结构,再也不提 class 的结构。dex 的结构能够用下面这张图表示:架构

dex 文件的结构其实很是清晰,分几个大块,header 区,索引区,data 区,map 区。本优化方案优化删除的就是 data 区中的 debugItems 区域。app

  • debugItem 干嘛用?

首先得知道 debugItem 里面存了什么? 里面主要包含两种信息:框架

  1. 函数的参数变量和全部的局部变量
  2. 全部的指令集行号和源文件行号的对应关系 有什么用呢: 第一点其实很明显,既然叫 debugItem,那么确定就是 debug 的时候用的喽,咱们平时在用 IDE 进行断点和单步调试的时候都会用到这个区域。 第二点做用那就是上报 crash 或者主动获取调用堆栈的时候用的,由于虚拟机真正执行的时候是执行的指令集,上报堆栈会上报 crash 的对应源文件行号,此时正是经过这个 debugItem 来获取对应的行号,能够用下面的截图比较直观的了解:

image.png | left | 732x336

上图是一个比较常见的 crash 信息,红框中的行号即是经过查找这个 debugItem 来获取的。模块化

  • debugItem 有多大?

在支付宝的场景下,debug 包有 4-5M,release 包有 3.5M 左右,占 dex 文件大小的比例在 5.5% 左右,和 google 官方的数据是一致的。若是能把这部分直接去掉,是否是很诱人!函数

  • debugItem 能直接去掉吗?

显然不能,若是去掉了,那全部上报的 crash 信息就会没有行号,全部的行号都会变成 -1,会被喷的找不到北。 其实在 proguard 的时候就是有配置能够去掉或保留这个行号信息,-keep SourceFile, LineNumberTable 就是这个做用,为了方便定位问题,基本全部的开发都保留了这个配置。 因此,方案的核心思路就是去掉 debugItem,同时又能让 crash 上报的时候能拿到正确的行号。至于 IDE 调试,这个比较好解决,咱们只要处理 release 包就好了,debug 包不处理。post

方案一

核心思路也比较简单,就是行号查找离线化,让原本存放在 App 中的行号对应关系提早抽离出来存放在服务端,crash 上报的时候经过提早抽离的行号表进行行号反解,解决 crash 信息上报无行号,没法定位的问题。 思路虽然简单,实现的时候仍是有点复杂,推进上线也比较曲折,方案通过几回调整,大概的方案能够用下面一张图来抽象:

image | left

如上图,核心点有四个:

  1. 修改 proguard,利用 proguard 来删除 debugItem (去掉 -keep lineNumberTable),在删除行号表以前 dump 出一个临时的 dex。
  2. 修改 dexdump,把临时的 dex 中的行号表关系 dump 成一个 dexpcmapping 文件(指令集行号和源文件行号映射关系),并存至服务端。
  3. hook app runtime 的 crash handler,把 crash 时的指令集行号上报到反解平台。
  4. 反解平台经过上报指令集行号和提早准备好 dexpcmapping 文件反解出正确的行号。

上面这套方案大概花了两个多星期,撸出了整个 demo,其它几个改造点都不是很难,难点仍是在指令集行号的上报。 咱们知道全部的 crash 最终都是会有一个 throwable 对象,里面保存了整个堆栈信息,通过反复的阅读源码和尝试,发现我要的指令集行号其实也在这个对象里面。能够用下面一幅简单的图示意:

在打印 crash 堆栈信息前,每一个 throwable 都会调用art虚拟机提供的一个 jni 方法,返回一个内部的对象叫 stackTrace 保存在 Throwable 对象中,这个 stackTrace 对象里面保存的即是整个方法的调用栈,固然也包括指令集行号,后续获取实际的堆栈信息时会再调用一个 art 的 jni 方法,把这个 stackTrace 方法丢过去,底层经过这个 stackTrace 对象中的指令集行号反解出正式的源文件行号。 好了,其实很简单,反射获取下这个 Throwable 中的 stackTrace 对象,拿到指令集行号,而后,上报。 这里要注意的一个点,比较恶心,每一个虚拟机的实现都不同,首先内部对象的名字,有些叫 stackTrace,有些叫 backstrace,而后这个内部对象的类型也很是有,有些是 int 数组,有些是 long 数组,有些是对象数组,可是都会有这个指令集行号,须要针对不一样的虚拟机版本使用不一样的方法去解析这个对象,大概要兼容4种虚拟机,4.x, 5.x, 6.x, 7.x,7.x 虚拟机以后的就统一了。

方案二

上面这套方案其实挺完美的,没有什么兼容性问题,删除是直接利用 proguard,获取指令集行号直接在 java 层获取,不须要各类 hook,若是只须要处理 crash 的上报,方案一足够了,可是在支付宝有不少场景是远远不够的。 好比:

  • 性能,CPU,内存异常时调用栈。
  • native crash 时的 Java 调用栈。

上面这些 case 都会涉及到堆栈信息,方案一中经过反射调用 throwable 中的 stackTrace 内部对象根本搞不定,须要换种方法。 最开始的思路是尝试 hook art 虚拟机,天天翻源码,看看能够 hook 的点,最后仍是放弃了,一个是担忧兼容性问题,另外一个是 hook 的点太多,比较慌。 最后换了一种思路,尝试直接修改 dex 文件,保留一小块 debugItem,让系统查找行号的时候指令集行号和源文件行号保持一致,这样就什么都不用作,任何监控上报的行号都直接变成了指令集行号,只需修改 dex 文件。能够用下面的示意图表示:

image | left

如上图:原本每个方法都会有一个 debugInfoItem,每个 debuginfoItem 里面都有一个指令集行号和源文件行号的映射关系,我作的修改其实很是简单,就是把多余的 debugInfoItem 所有删掉了,只留了一个 debugInfoItem,全部的方法都指向同一个 debugInfoItem,而且这个 debugInfoItem 中的指令集行号和源文件行号保持一致,这样无论用什么方式来查行号,拿到的都是指令集行号。

其中也踩过不少坑,其实光留一个 debugInfoItem 是不够的,要兼容全部虚拟机的查找方式,须要对 debugInfoItem 进行分区,而且 debugInfoItem 表不能太大,遇到过一个坑就是 androidO 上进行 dex2oat 优化的时候,会频繁的遍历这个 debugInfoItem,致使 AOT 编译比较慢,最后都经过 debugInfoItem 分区解决了。

这个方案比较完全,不用改 proguard,也不用 hook native。不过若是只须要处理 crash 的行号问题,那仍是首推方案一,这个方案改动有点大,前期也是天天研究 dex 的文件结构,抠每个细节,有比较大的把握时才敢改。

小结

目前该方案已经在支付宝正式上线,前面通过好几轮的外灰验证,仍是比较稳定的。支付宝总体包大小减小了 2.1M 左右,真实的 dex 大小减小 3.5M 左右。

经过本节内容,咱们初步了解了支付宝在 Android 客户端如何经过包大小压缩以提高 App 运行效率和质量。因为篇幅限制,不少技术要点咱们没法一一展开。而相应的技术内核,咱们一样应用在了 mPaaS 并对外输出,欢迎你们上手体验:

tech.antfin.com/docs/2/4954…

关于 Android 端包大小压缩的设计思路和具体实践,一样期待大家的反馈,欢迎一块儿探讨交流。

往期阅读

《开篇 | 模块化与解耦式开发在蚂蚁金服 mPaaS 深度实践探讨》

《支付宝移动端动态化方案实践》

《支付宝客户端架构解析:iOS 容器化框架初探》

《支付宝客户端架构解析:Android 容器化框架初探》

《支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」》

《支付宝 App 构建优化解析:经过安装包重排布优化 Android 端启动性能》

关注咱们公众号,得到第一手 mPaaS 技术实践干货

QRCode
相关文章
相关标签/搜索