热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

android压缩大小打包_支付宝App构建优化:Android包大小极致压缩

点击上方“刘望舒”,选择“星标”多点在看,就是真爱作者:琉克|来源:公众号mPaaS1前言本章节我们将围绕《支付宝App构建优化解析》另启

点击上方“刘望舒”,选择“星标”

 多点在看,就是真爱

作者: 琉克 |  来源:公众号 mPaaS

1

前言

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

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

2

背景

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

3

方案介绍

支付宝关于包大小优化,支付宝一直在这个方向上努力,因此我们引入了很多方案,比如 “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 的结构可以用下面这张图表示:

2965cf19d5fe7cf3d5703e0e7c984bb2.png

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

  • debugItem 干吗用?

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

  1. 函数的参数变量和所有的局部变量

  2. 所有的指令集行号和源文件行号的对应关系有什么用呢?

    第一点很明显,既然叫 debugItem,那么肯定就是 debug 的时候用。我们平时在用 IDE 进行断点和单步调试的时候都会用到这个区域。

    第二点作用那就是上报 crash 或者主动获取调用堆栈的时候用。因为虚拟机真正执行的时候是执行的指令集,上报堆栈会上报 crash 的对应源文件行号,此时正是通过这个 debugItem 来获取对应的行号,可以用下面的截图比较直观的了解:

9b8836cd675a0979bc2cd1c205f42ed0.png

上图是一个比较常见的 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 包不处理。

4

方案一

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

d1636ef96bc07507ff01fc3e6eead275.png

如上图,核心点有四个:

  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 对象,里面保存了整个堆栈信息,经过反复的阅读源码和尝试,发现我要的指令集行号其实也在这个对象里面。可以用下面一幅简单的图示意:

86100a2ec389ce6b1b235ca08629b58e.png

在打印 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 虚拟机之后的就统一了。

5

方案二

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

  • 性能,CPU,内存异常时调用栈。

  • native crash 时的 Java 调用栈。

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

5ffba81c4c2f98f4c413b84c8c123e45.png

如上图:本来每一个方法都会有一个 debugInfoItem,每一个 debuginfoItem 里面都有一个指令集行号和源文件行号的映射关系,我做的修改其实非常简单,就是把多余的 debugInfoItem 全部删掉了,只留了一个 debugInfoItem,所有的方法都指向同一个 debugInfoItem,并且这个 debugInfoItem 中的指令集行号和源文件行号保持一致,这样不管用什么方式来查行号,拿到的都是指令集行号。其中也踩过很多坑,其实光留一个 debugInfoItem 是不够的,要兼容所有虚拟机的查找方式,需要对 debugInfoItem 进行分区,并且 debugInfoItem 表不能太大,遇到过一个坑就是 androidO 上进行 dex2oat 优化的时候,会频繁的遍历这个 debugInfoItem,导致 AOT 编译比较慢,最后都通过 debugInfoItem 分区解决了。这个方案比较彻底,不用改 proguard,也不用 hook native。不过如果只需要处理 crash 的行号问题,那还是首推方案一,这个方案改动有点大,前期也是每天研究 dex 的文件结构,抠每一个细节,有比较大的把握时才敢改。

6

小结

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

通过本节内容,我们初步了解了支付宝在 Android 客户端持续压缩包大小所积累的思考和实践。由于篇幅限制,很多技术要点我们无法一一展开。而相应的技术内核,我们同样应用在了 mPaaS 并对外输出,欢迎大家上手体验:

https://tech.antfin.com/docs/2/49549

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

----------  END  ----------

推荐文章

不会查看系统源码,还搞什么Android?

Flutter 美团的实践及原理

为什么阿里禁止在 foreach 循环里进行元素的 remove/add 操作

分享大前端、Java、Android等技术,

关注职业发展和行业动态。

ce7e3c18224defa0fa4cafe826539ba3.png




推荐阅读
  • 字节码开发笔记:深入解析与应用技巧 ... [详细]
  • 目录预备知识导包构建数据集神经网络结构训练测试精度可视化计算模型精度损失可视化输出网络结构信息训练神经网络定义参数载入数据载入神经网络结构、损失及优化训练及测试损失、精度可视化qu ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 通过将常用的外部命令集成到VSCode中,可以提高开发效率。本文介绍如何在VSCode中配置和使用自定义的外部命令,从而简化命令执行过程。 ... [详细]
  • 解决Only fullscreen opaque activities can request orientation错误的方法
    本文介绍了在使用PictureSelectorLight第三方框架时遇到的Only fullscreen opaque activities can request orientation错误,并提供了一种有效的解决方案。 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 在 Vue 应用开发中,页面状态管理和跨页面数据传递是常见需求。本文将详细介绍 Vue Router 提供的两种有效方式,帮助开发者高效地实现页面间的数据交互与状态同步,同时分享一些最佳实践和注意事项。 ... [详细]
  • Netty框架中运用Protobuf实现高效通信协议
    在Netty框架中,通过引入Protobuf来实现高效的通信协议。为了使用Protobuf,需要先准备好环境,包括下载并安装Protobuf的代码生成器`protoc`以及相应的源码包。具体资源可从官方下载页面获取,确保版本兼容性以充分发挥其性能优势。此外,配置好开发环境后,可以通过定义`.proto`文件来自动生成Java类,从而简化数据序列化和反序列化的操作,提高通信效率。 ... [详细]
  • 揭秘腾讯云CynosDB计算层设计优化背后的不为人知的故事与技术细节
    揭秘腾讯云CynosDB计算层设计优化背后的不为人知的故事与技术细节 ... [详细]
  • 本文详细解析了 MySQL 5.7.20 版本中二进制日志(binlog)崩溃恢复机制的工作流程。假设使用 InnoDB 存储引擎,并且启用了 `sync_binlog=1` 配置,文章深入探讨了在系统崩溃后如何通过 binlog 进行数据恢复,确保数据的一致性和完整性。 ... [详细]
  • JVM参数设置与命令行工具详解
    JVM参数配置与命令行工具的深入解析旨在优化系统性能,通过合理设置JVM参数,确保在高吞吐量的前提下,有效减少垃圾回收(GC)的频率,进而降低系统停顿时间,提升服务的稳定性和响应速度。此外,本文还将详细介绍常用的JVM命令行工具,帮助开发者更好地监控和调优JVM运行状态。 ... [详细]
  • Hadoop平台警告解决:无法加载本机Hadoop库的全面应对方案
    本文探讨了在Hadoop平台上遇到“无法加载本机Hadoop库”警告的多种解决方案。首先,通过修改日志配置文件来忽略该警告,这一方法被证明是有效的。其次,尝试指定本地库的路径,但未能解决问题。接着,尝试不使用Hadoop本地库,同样没有效果。然后,通过替换现有的Hadoop本地库,成功解决了问题。最后,根据Hadoop的源代码自行编译本地库,也达到了预期的效果。以上方法适用于macOS系统。 ... [详细]
  • 为了在Hadoop 2.7.2中实现对Snappy压缩和解压功能的原生支持,本文详细介绍了如何重新编译Hadoop源代码,并优化其Native编译过程。通过这一优化,可以显著提升数据处理的效率和性能。此外,还探讨了编译过程中可能遇到的问题及其解决方案,为用户提供了一套完整的操作指南。 ... [详细]
  • 在本文中,我们探讨了如何使用 NSArrays 来实现集合的交集与并集操作。通过两个示例数组 A 和 B,其中包含一些共同元素(例如 A: 1, 2, 3 和 B: 2, 3, 4),我们将详细介绍如何高效地进行这些集合操作。此外,我们还将讨论这些方法在实际应用中的性能优势和适用场景。 ... [详细]
author-avatar
知足者常乐-----仙_230
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有