共计 7185 个字符,预计需要花费 18 分钟才能阅读完成。
01 前言
此前百度 APP 曾经具备根本的包体积优化机制、束缚和意识,但作为巨舰型 APP,业务的高速迭代依然不可避免地造成了包体积爆炸式增长。包体积或间接或间接地影响着下载转化率、安装时间、运行内存、磁盘空间等重要指标,所以投入精力排除积弊、挖掘更深层次的体积优化项是十分必要的。
依据谷歌商店的外部数据,APK 体积每缩小 10M,均匀可减少~1.5% 的下载转化率,如下图所示:
<p align=center> 图 1 谷歌商店利用转化率减少幅度 / 10M [1]</p>
Android 包体积优化伎俩有很多,比方业务裁剪、插件化、混合开发、资源后下发等。本系列文章次要针对的是业务无关、集成在 APK 中的内容的体积优化,如 Dex 优化、资源优化、so 优化等,咱们称之为根底机制优化。
包体积根底机制优化实际将会以系列文章的形式出现,次要包含以下局部:心路历程、Dex 行号优化残缺计划、资源优化实际与摸索、Dex 优化实际与摸索、so 优化摸索、其余优化教训与总结。
本文讲述的是百度 APP 包体积根底机制优化心路历程,包含起继续指导作用的根本思维、优化对象剖析、对现有优化工具的学习、以及最终产出的体积优化项。
02 根本思维
2.1 分而治之
咱们的优化对象不只是 APK 这个最终产物,也包含 APK 中的内容,这些内容的体积优化思路与伎俩不尽相同。
2.2 可继续优化
好的优化机制不止失效于当下,也失效于将来。举例来说,从源码仓库删除以后的 Dead Code 属于一次性存量优化操作,而编译器的 DCE 机制 (Dead Code Elimination) 可继续失效于将来产生的 Dead Code。从长线思考,咱们应优先建设后者,而后倒推前者的执行。
2.3 站在前人的肩膀上
包体积优化并不是一个陈腐的话题,Android 官网和开发者们都在继续致力于优化体积。反复造轮子是不被提倡的,但对于不同的利用场景,尤其是巨舰型 APP,体积优化应该有定制化的计划。
2.4 明确代价,有所取舍
依据热力学第一定律,收益不会凭空产生,肯定会随同着代价,例如人力的投入、编译工夫的减少、适配难度的减少等。明确代价后,咱们能力决定某优化项是否要做、何时做、如何做。
2.5 束缚与意识
除了自动化的优化机制,还须要配套有自动化的体积增长束缚,同时从源头晋升开发者的体积优化意识,多管齐下能力达到最优成果。
03 APK 构造剖析
接下来咱们会简略剖析下 APK 内各组成部分,以及 APK 作为 ZIP,其规范构造是什么样的。
3.1 APK 内容分析
<p align=center> 图 2 APK 构造 </p>
- 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 构造剖析
<p align=center> 图 3 ZIP 规范构造示意图 </p>
- 压缩源文件信息
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 产物。
<p align=center> 图 4 Proguard 解决对象及作用示意图 [9]</p>
ProGuard 的优化操作次要包含:
缩减:平安移除无用类、办法、字段和属性。
混同:缩短类与成员的名称。
优化:指令级别的优化,合并反复指令、清理无用指令、晋升指令执行效率。
4.2 R8
AGP 3.3 之后官网开始举荐应用 R8,R8 与 ProGuard 不只是简略的代替关系,它还将脱糖、D8 整合到了一起,极大的晋升了构建效率。
图 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 社区):
<p align=center> 图 6 resources.arsc 结构图 </p>
- 资源同名化
在理论利用中,咱们默认通过资源 id 查找资源内容,对资源名的应用频率非常低,仅限于通过资源名反查资源 id 以及 通过资源 id 获取资源名两种状况。所以资源项名称字符串池所占据的空间即是咱们的优化对象。极限优化后果是,这个池子里仅寄存一个字符串,所有 ResTable\_entry 的资源项名称 index 均指向这个池子里仅有的字符串,即所有资源的名字都变得一样了。理论场景中,咱们会有豁免和降级为混同的需要,例如通过资源名反向查问资源 id 等状况。
- 资源文件门路优化
与 AndResGuard 中的资源门路混同成果类似,都是尽可能缩短资源文件的门路长度,从而缩小 ResTable\_entry 的 value 大小。咱们将门路 Hash 值转换到碰撞起码的位数,作为最终的混同后果,其长处在于混同后果基本上是固定的,无需 applymapping。除此之外,咱们还较为激进地去掉了大部分文件的后缀名。
- 资源配置优化
从 arsc 中的资源 id 蕴含了偏移量信息,零碎通过偏移量在 arsc 中定位资源。所以图 7 中的空白区域必须保留一个 4 字节的占位,以满足偏移量查问形式。咱们正在对此局部做优化,主旨是通过优化不必要的 configuraion,达到缩小对齐占位的目标。
<p align=center> 图 7 resources.arsc 空白占位示意图 </p>
- 图片压缩
因为 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 位对齐,否则会间接装置失败。
<p align=center> 图 8 Android11 强调 resources.arsc 压缩对齐问题 </p>
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 包体积大小比照如下:
——————END——————
参考资料:
[1] 包大小与装置转化率
https://medium.com/googleplay…
[2] ZIP 格局
https://pkware.cachefly.net/w…
[3] ProGuard
https://www.guardsquare.com/p…
[4] R8
https://r8.googlesource.com/r8
[5] ProGuard 与 R8 比照
https://www.guardsquare.com/b…
[6] AndResGuard
https://github.com/shwenzhang…
[7] ByteX
https://github.com/bytedance/…
[8] Booster
https://github.com/didi/booster
[9] AGP
https://developer.android.com…
[10] Titan-Dex
https://github.com/baidu/tita…
[11] zopfli
https://en.wikipedia.org/wiki…
举荐浏览:
百度 APP iOS 端内存优化实际 - 大块内存监控计划
百家号基于 AE 的视频渲染技术摸索
百度工程师教你玩转设计模式(观察者模式)
Linux 通明大页机制在云上大规模集群实际介绍
超高效!Swagger-Yapi 的机密
百度直播 iOS SDK 平台化输入革新