Baidu APP Android Package Size Optimization Practice (1) Overview

picture

01 Preface

Previously, Baidu APP already had the basic package volume optimization mechanism, constraints and awareness, but as a giant ship-type APP, the high-speed iteration of business still inevitably caused the explosive growth of package volume. Package volume directly or indirectly affects important indicators such as download conversion rate, installation time, running memory, disk space, etc. Therefore, it is necessary to invest energy to eliminate accumulated defects and explore deeper volume optimization items.

According to the internal data of Google Store, every 10M decrease in APK size can increase the download conversion rate by ~1.5% on average, as shown in the following figure:

picture

Figure 1 Google Store App Conversion Rate Increase / 10M [1]

There are many ways to optimize the size of Android packages, such as business tailoring, plug-in, hybrid development, and post-resource distribution. This series of articles is mainly aimed at the volume optimization of content that is irrelevant to business and integrated in APK, such as Dex optimization, resource optimization, so optimization, etc., which we call basic mechanism optimization.

The optimization practice of the basic mechanism of package volume will be presented in the form of a series of articles, mainly including the following parts: the journey of the mind, the complete solution of Dex line number optimization, resource optimization practice and exploration, Dex optimization practice and exploration, so optimization exploration, other optimization experience and Summarize.

This article describes the process of optimizing the basic mechanism of Baidu APP package volume, including the basic ideas that play a continuous guiding role, the analysis of optimization objects, the learning of existing optimization tools, and the final output volume optimization items.

02 Basic idea

2.1 Divide and conquer

Our optimization object is not only the final product of APK, but also the content in the APK. The ideas and methods of volume optimization of these content are different.

2.2 Sustainable optimization

A good optimization mechanism not only takes effect in the present, but also in the future. For example, deleting the current Dead Code from the source code repository is a one-time inventory optimization operation, and the compiler's DCE mechanism (Dead Code Elimination) continues to take effect in the future Dead Code. From a long-term perspective, we should prioritize the establishment of the latter, and then reverse the execution of the former.

2.3 Standing on the shoulders of predecessors

Package size optimization is not a new topic, Android officials and developers are continuing to work on optimizing the size. Repeatedly building wheels is not recommended, but for different application scenarios, especially giant ship APPs, there should be customized solutions for volume optimization.

2.4 Specify the price and make trade-offs

根据热力学第一定律,收益不会凭空产生,一定会伴随着代价,例如人力的投入、编译时间的增加、适配难度的增加等。明确代价后,我们才能决定某优化项是否要做、何时做、如何做。

2.5 约束与意识

除了自动化的优化机制,还需要配套有自动化的体积增长约束,同时从源头提升开发者的体积优化意识,多管齐下才能达到最优效果。

03 APK结构分析

接下来我们会简单分析下APK内各组成部分,以及APK作为ZIP,其标准结构是什么样的。

3.1 APK内容分析

picture

图2 APK 结构

  • classes.dex
    APK 中可能包含一个或多个 classes.dex 文件,应用程序内的 Java/Kotlin 源码最终会以 dalvik 字节码的方式存在于 classes.dex 文件中。

  • resources.arsc
    该文件是包含配置信息的资源查询表,起着链接代码与资源的作用。Dex 文件中的 R.class 仅包含资源 id,AssetManager 会利用 id 到 arsc 表中查询与当前设备信息最匹配的资源文件路径(或资源内容)。

  • res/
    包含源码工程中 res 目录下除了 values 外的资源文件,这些文件路径同时会体现在 resources.arsc 中。

  • lib/
    native libraries。即源码工程 jni 目录下的 so 文件,二级目录必须为 NDK支持的 ABI。

  • assets/
    与 res/ 资源目录不同,assets/ 下的资源文件不会在 resources.arsc 中生成查询条目,且 assets/ 下的资源目录可完全自定义,业务代码获取 assets 资源和 res 资源的方式也完全不同。

  • META-INF/
    应用签名信息。该目录在应用签名后生成,包含以下三个文件:
    MANIFEST.MF:摘要文件,包含APK内所有文件的路径及其 SHA1/SHA256 值。
    CERT.SF:对摘要的签名文件,包含APK内所有文件的路径,及其在 MANIFEST.MF 中对应信息的 SHA1/SHA256 值。
    CERT.RSA:保存公钥、加密算法及其私钥加密后的内容。

  • AndroidManifest.xml
    应用清单文件,用于描述应用基本信息,主要包括应用包名、应用id、应用组件、所需权限、设备兼容性等。

3.2 ZIP结构分析

picture

图3 ZIP 标准结构示意图

  • 压缩源文件信息
    Local file header:描述源文件信息。
    File data:源文件数据。
    Data descriptor:校验码及压缩前后大小。

  • 中心目录区
    Central directory
    记录 ZIP 目录结构。每一条 file header 对应一个源文件,描述文件相关信息。

  • 中心目录结束标识
    End of central directory record
    标识 ZIP 包结束,包含 ZIP 包及中心目录的简要信息。

04 现有优化工具介绍

对于发展初期的应用,体积优化的优先级较低,直接使用以下体积优化工具是性价比最高的选择。百度APP 同样对比借鉴了以下工具,从中衍生出了全新的、定制化的优化需求。

4.1 ProGuard

在 AGP3.3 之前,ProGuard 作为官方体积优化工具,负责在编译完成之后对class 文件进行缩减混淆等操作,其优化结果交给 Dx/D8 转化为 Dex 产物。

picture

图4 Proguard 处理对象及作用示意图 [9]

ProGuard 的优化操作主要包括:

缩减:安全移除无用类、方法、字段和属性。

混淆:缩短类与成员的名称。

优化:指令级别的优化,合并重复指令、清理无用指令、提升指令执行效率。

4.2 R8

AGP 3.3之后官方开始推荐使用 R8,R8 与 ProGuard 不只是简单的替代关系,它还将脱糖、D8 整合到了一起,极大的提升了构建效率。

picture

图5 R8 处理对象及作用示意图 [9]

R8 基本兼容此前的 ProGuard 规则,但仍存在些许差异(applymapping、行号处理、Kotlin元数据处理、无用判定等)。R8 不再高优考虑兼容性问题后,两者会派生出越来越多的不同点,建议定期关注,博采众长。

丨Jack & Jill

小插曲:官方在2015年推行过一段时间的Jack & Jill工具,它甚至把javac也囊括了进来,算是真正实现了端到端的编译。但Jack的性能与生态相比javac实在差距太大,官方出于成本考虑最后还是弃坑了。

4.3 AndResGuard

AndResGuard 是微信推出资源优化工具。它的基本思想类似于 ProGuard 中的混淆,体积优化是它的附加收益,同时还提供了压缩、加密等选项。

4.4 ByteX

ByteX 是字节开源的一套Java字节码插桩工具,目前主要包括优化与检查工作,其中一些子项最终会带来体积收益。包括R类内联、移除debug信息、access 方法内联等。

4.5 Booster

Booster 是滴滴开源的一套质量优化框架,其中包括体积优化专项,例如资源文件压缩、资源产物.ap_ 压缩、去冗余资源、R类内联、DataBinding BR内联等。

4.6 AGP

Android Gradle Plugin(AGP)包含了多个体积优化任务,提供了许多优化配置项,大部分任务已经作为APK打包的标配。


一般来讲,我们的优化任务会依赖于这些任务的执行。如果定制的优化无法兼容现存任务,则需要关闭或hook这些任务。接下来将按照编译顺序简单介绍几个优化任务与配置:

  • OptimizeResources
    AGP4.2+ 新增的资源优化任务,目前只实现了资源文件路径的缩短,默认开启,可通过 android.enableResourceOptimizations 关闭。

  • StripSymbols
    NDK 会利用 llvm-strip 移除掉 native libraries 中的unneeded symbols,这部分优化工作也可以放在so编译期间完成。

  • MinifyWithR8/ProGuard
    利用 R8 或 ProGuard 实现代码优化,此处就不再赘述了。

  • ShrinkResources
    由 ShrinkResources 开关控制,启用前提是必须开启 minifyEnable。其作用是将未被引用的资源文件替换为一个体积很小的格式文件(仍存在占位体积,同时保留了该资源条目,所以 resources.arsc 体积并不会减少),可通过 res/raw/keep.xml 文件配置 shrinkMode 和白名单。

  • PackageOptions
    打包时选项,包括过滤 exclude、相同文件仅打包 pickFirst、全部打包 merge、so优化豁免 doNotStrip。

  • Splits
    分包/过滤策略,配置项包括 ABI、资源配置(语言、分辨率等)。

05 百度APP优化项项概览

5.1 Dex优化

百度APP 实现 Dex 的体积优化项可以分为两类:源码编译期间的优化;APK 打包期间对 Dex 文件的优化。两者的区别主要是优化对象不同,所以基于不同的优化工具实现,前者基于Java字节码工具实现(如 ASM),后者基于 Dex 字节码工具实现(如 Titan-Dex [10])。

丨Titan-Dex

Titan-Dex 是百度开源的面向Android Dalvik(ART)字节码(bytecode)格式的操纵框架,可以在二进制格式下实现修改已有的类,或者动态生成新的类。百度Titan-hotfix工具即基于此框架实现。

  • R类优化

工程组件越多,R类所占体积越大,未关闭资源依赖传递的情况下则更严重。我们在编译期将代码中调用 R.type.name 的地方全部替换成了对应的id常量,最终 R.class 会作为无用类被 R8/ProGuard 清理掉。

  • 行号优化

Dex 中的 debug 区域占5~10%的大小,但其最大的作用是分析崩溃堆栈时定位。该区域可以通过去除 ProGuard 规则 -keepattributes SourceFile,LineNumberTable 完全移除。我们选择在指令级别完成 debug infos 的映射与复用,同时联动百度性能平台(目前仅供公司内部使用,功能可类比腾讯 bugly)完成崩溃堆栈的还原,既优化了体积,又不会影响堆栈的分析。

  • 注解优化

Dex中注解分为三种类型:Build、Runtime、System。Build 和 Runtime对应 ProGuard 规则 -keepattributes *Annotation*,可优化的 System 注解根据具体类型分别对应 -keepattributes InnerClasses, Signature, EnclosingMethod。跟行号一样,可以通过去除这些规则完成一刀切的优化。但由于我们接入的三方组件自带这些 ProGuard 规则,且部分类的 System 注解有保留的需要,我们选择后置地处理 Dex 文件,基于 Dex 字节码工具完成目标注解的移除。

5.2 资源优化

资源优化的对象分为两类,一是资源查询表 resources.arsc,部分优化操作会涉及到 res/ 及 R文件的修改,但本质都是从 resources.arsc 出发的;二是原始资源文件,包括 res/和 assets/。

介绍优化项前,我们先看一张网上最经典的 resources.arsc 结构图(来源CSDN社区):

picture

图6 resources.arsc 结构图

  • 资源同名化

在实际应用中,我们默认通过资源 id 查找资源内容,对资源名的使用频率十分低,仅限于通过资源名反查资源 id 以及 通过资源 id 获取资源名两种情况。所以资源项名称字符串池所占据的空间即是我们的优化对象。极限优化结果是,这个池子里仅存放一个字符串,所有 ResTable_entry 的资源项名称 index均指向这个池子里仅有的字符串,即所有资源的名字都变得一样了。实际场景中,我们会有豁免和降级为混淆的需求,例如通过资源名反向查询资源id等情况。

  • 资源文件路径优化

与 AndResGuard 中的资源路径混淆效果相似,都是尽可能缩短资源文件的路径长度,从而减少 ResTable_entry 的 value 大小。我们将路径 Hash 值转换到碰撞最少的位数,作为最终的混淆结果,其优点在于混淆结果基本上是固定的,无需 applymapping。除此之外,我们还较为激进地去掉了大部分文件的后缀名。

  • 资源配置优化

从 arsc 中的资源 id 包含了偏移量信息,系统通过偏移量在 arsc 中定位资源。所以图7中的空白区域必须保留一个4字节的占位,以满足偏移量查询方式。我们正在对此部分做优化,宗旨是通过优化不必要的 configuraion,达到减少对齐占位的目的。

picture

图7 resources.arsc 空白占位示意图

  • 图片压缩

由于 webp 格式受限于 minsdkversion18,我们目前还是针对 png 图片做压缩优化,使用的工具包括 TinyPng 和 ImageOptim。除了出图阶段压缩外,也会有后置流水线做压缩检查。

  • 无用资源清理

如4.5中提到的,ShrinkResources 并不会真正移除未被引用的资源文件。不过我们可以拿到被 shrink 的 resources 列表,然后再利用资源优化工具做真正的删除。

丨New ShrinkResource

2022.1发布的 AGPv7.1.0 更新了资源缩减功能,添加了实验性选项 android.experimental.enableNewResourceShrinker.preciseShrinking,该选项设置为true后 ShrinkResources 会完全移除未使用的文件资源及 value 资源,但 arsc 中仍会存在这些资源的填充占位。

  • arsc 压缩

resources.arsc 的压缩体积收益很高,但对其进行压缩会影响启动速度和内存指标。具体原因是:系统在加载 arsc 文件时,若 arsc 文件未压缩,可使用 mmap 进行内存映射;若 arsc 文件被压缩了,则需要将其解压缩后读取到RAM 缓冲区,会增加内存使用,也会拖慢启动速度。在业界大都压缩 arsc 的情况下,百度APP 出于综合考量一直未对 arsc 文件进行压缩。无独有偶,官方出于同样的考虑,从 Android11 开始强制要求 resources.arsc 不可压缩且保持4位对齐,否则会直接安装失败。

picture

图8 Android11强调 resources.arsc 压缩对齐问题

5.3 ZIP优化

  • 压缩

目前我们使用的压缩算法有 7z 和 zopfli[11],后者压缩率和压缩耗时都有明显增加,稳定性还在验证中。采用新的压缩算法时需特别注意两点,一是不要压缩 resources.arsc;二是注意压缩、 对齐、签名操作的顺序。

  • 文件路径优化

如章节3.2 ZIP结构分析所示,APK 中有三处体积与文件路径长度相关:META-INF/、压缩源文件数据区的 local file header、中心目录区的 file header,资源文件路径优化效果同样会体现在这里。同理控制 assets/ 下的文件路径也可带来体积收益。

5.4 其他

  • 夜间模式优化

目前百度APP的夜间资源是一个 APK 包,此前实现方式是与主包中的资源名保持一致,通过反射的方法查询对应 id。现改为 id 一致,这样既避免了反射查询的耗时,章节 5.2中的资源优化也可以应用到资源 APK,进一步减小体积。

  • 混淆规则

ProGuard/R8 提供了多种多样的规则用以豁免代码优化操作,如果使用不当可能会造成体积的白白浪费。未来我们计划制定一套详细的 ProGuard 规则使用规范,并对每个组件的 ProGuard 规则都进行校验,例如不允许出现本组件包名范围外的 keep 规则、不允许出现包级别 keep 等。

  • 体积流水线

主要包括仓库体积约束流水线,二进制组件体积检查流水线,以及 APK 组成体积分析流水线,分阶段进行约束与分析。目前百度APP 建设了 APK 体积监控流水线,每当主线有代码合入、触发编译打包后,会即时对编译产物 APK 做体积分析,并与上一次编译产物进行比对,可以马上发现异常的体积增长。

06 总结

第五章介绍的优化项自21年8月至22年2月分批次上线,期间业务依旧在高速迭代,虽然有体积监控流水线作用,包体积仍会不可避免地增加。由于优化机制十分底层,需进行充分的线下测试与线上小流量灰度,验证稳定性后才能正式上线。上线/灰度前后百度APP 包体积大小对比如下:

优化项 优化前体积 优化后体积 Diff
Dex 行号优化 & 混淆规则收敛 (上线) 123.58MB 119.37MB 4.21MB
资源名/路径优化 & 7z 压缩 & 夜间模式优化 (上线) 122.54MB 116.36MB 6.18MB
图片压缩 & 热修插桩优化 (上线) 117.07MB 116.00MB 1.07MB
Dex 注解优化 (灰度) 117.16MB 115.95MB 1.21MB
启用R8 (灰度) 119.06MB 116.62MB 2.44MB
zopfli 压缩 & 资源缩减 & 资源配置优化 (灰度) 119.57MB 116.65MB 2.92MB

This article mainly introduces the basic idea of ​​Baidu APP package volume optimization, deconstructs the optimization objects, introduces the existing optimization tools, and finally briefly introduces the optimization items and benefits of Baidu APP specific practice. In the future, we will introduce the principle and implementation of each optimization type in detail, and gradually open source general volume optimization tools, so stay tuned.

—————————END————————

References:

[1] Package size and install conversion rate

medium.com/googleplayd…

[2] ZIP format

pkware.cachefly.net/webdocs/APP…

[3] ProGuard

www.guardsquare.com/proguard

[4] R8

r8.googlesource.com/r8

[5] Comparison of ProGuard and R8

www.guardsquare.com/blog/progua…

[6] AndResGuard

github.com/shwenzhang/…

[7] ByteX

github.com/bytedance/B…

[8] Booster

github.com/didi/booste…

[9] AGP

developer.android.com/studio/rele…

[10] Titan-Dex

github.com/baidu/titan…

[11]

en.wikipedia.org/wiki/Zopfli

Recommended reading:

Baidu APP iOS side memory optimization practice - large block memory monitoring solution

Baijiahao's AE-based video rendering technology exploration

Baidu engineers teach you to play with design mode (observer mode)

Introduction to the practice of Linux transparent huge page mechanism in large-scale clusters on the cloud

Super efficient! Swagger-Yapi's Secret

Baidu Live iOS SDK platform output transformation

Guess you like

Origin juejin.im/post/7121938110585241630