口红|5年磨一剑|优酷Android包瘦身治理思路全解( 八 )


首先来看远程so 。 动态链接库so与其它代码的耦合度低 , 在apk中具有较强的独立性 , 同时占用apk体积相对较大 , 因此单独将so进行远程化往往具有很高的瘦身投入产出比 。
第二种远程bundle , 一般是指可以完整的将一块功能进行远程化 , 远程部分相当于一个迷你apk(dex、resource、so) 。 远程bundle相关技术“历史悠久” , 动态化、插件化、组件化等虽然有语境、功能以及设计思想上的区别 , 但在技术上有着很多相似的地方 , 随着新版本os加强了对系统API调用、拦截和替换等方面限制 , 这个领域的主流技术方案逐渐演变为对系统侵入越来越轻量的方向 。 相对于远程so , 远程bundle在实现上要复杂很多:在运行时阶段 , 虽然对系统API的侵入性比较小 , 但是对唤端、组件路由跳转、后台Activity销毁重建等情况依然需要小心处理;在构建阶段 , 由于要“分离”出一个迷你apk , 因此对构建体系的兼容非常困难 , 目前业界有一些同类框架 , 很多时候并不是运行时无法满足需求 , 而是在构建侧无法做到很好的兼容性和易用性 。
最后一种远程资源 , 是指针对资源文件的远程化 。 可以通过将资源上传到文件托管平台 , 获取文件url后直接下载并使用(优酷采用的就是这种方式) 。 远程资源的实际应用场景较少 , 投入产出比也不高 , 所以目前优酷没有专门研发一个这样的框架 。 当然 , 如果有大量资源需要进行这种远程化改造 , 那可能有必要开发一个专用框架 。
4.2 整包瘦身
整包瘦身 , 是指在apk构建阶段整体进行处理的一类瘦身技术 , 对全部apk元素均可生效(包括无源码的二、三方sdk) , 新增代码也可以立刻得到同样的处理 , 其核心特点可以归纳为“中间拦截 , 整体生效” 。 也正是由于上述特点 , 这类瘦身的影响范围较广 , 因此在首次应用到app时 , 如何控制好验证成本和线上风险变得非常关键 , 当然在瘦身效果上 , 一般可以立竿见影的获得较大收益 。 自定义的一些整包瘦身方案 , 往往容易出现处理逻辑考虑不周全而导致的稳定性问题 , 注意这不属于技术方案本身的特点 , 而是具体实现代码的问题 。 在工程效能方面 , 对代码质量无影响 , 构建耗时则一般会有增加 , 有些整包瘦身技术会改变apk中目标元素形态 , 因此对各类相关问题分析会带来一定程度的效率降低 。
这里划分了14项整包瘦身技术 , 其中Android官方没有提供的能力 , 都已经沉淀到了优酷自研gradle plugin中 , 目前正在开源筹备中 。
【代码】Proguard/R8 。 利用Proguard工具 , 对java代码进行裁剪、混淆、优化处理 , 从而实现无用代码删除、符号(类、变量、方法)混淆、代码逻辑优化 , 包体积降低效果非常显著 。 值得注意的是 , google官方已经在近几年的Android Gradle Plugin中 , 使用自研的R8替代了java领域传统的Proguard工具 , 裁剪和优化效果更为强大 , 进一步压缩包大小的同时处理耗时更低 , 优酷从proguard切换到R8后 , 包大小降低约4.8%(3.2MB) , 构建耗时降低约25%(2min) , 当然这也和原progaurd的全局配置有关 , 尤其是优化次数-optimizationpasses 。【代码】D8 。 在apk构建过程中 , java代码需要经历由jvm字节码到dalvik字节码的转换处理 , DX/D8就是承担这个责任的工具 。 在优酷的具体实践中 , 由DX升级到D8后 , 包大小降低约9.5%(9.7MB) , 由于额外对dex合并进行了优化 , dex数量降低 , 导致包大小收益出现一次跃升 , 因此比官方给出的Benchmark收益5%要更高 。【代码】R类合并 。 将所有模块package.R类移除 , 并将java代码中对前者的引用统一替换为.R类 , 以此来降低包大小的一种技术手段 。 在优酷当前情况下(模块800多个 , dex24MB) , R类裁剪可以减少80万个java类Field , 带来近5MB包大小收益 。 由于每个dex中Field数量也受到65536限制 , 因此Field数量大幅减少所带来的dex数量减少 , 是瘦身收益的主要来源 。 进一步 , 可以把所有java代码中R..的引用 , 也全部替换为对应id值 , 这样.R类也可以删除 , 但是在已经完成R类合并的情况下 , 这个处理的收益比较有限 , 因此优酷并没有实际投入研发和使用 , 但是如果追求极致瘦身确实可以这么做! 【代码】Dex排布优化 。 Dex排布优化是指通过合理安排dex中包含的类 , 从而尽可能减少常量池冗余度以及dex数量 , 进而降低dex整体大小的一种瘦身技术 。 由于历史原因 , Dalvik字节码中调用method和field指令的操作数是16位 , 因此一个dex中method和field数量上限均为65536 , 而现代app一般都会包含多个dex , dex数量过多会导致各类常量池冗余度变高 , 从而导致包大小增加 。 事实上 , Dex排布优化不仅可以用于降低包大小 , 还可以通过选择不同的优化策略 , 来提升app运行时的性能 , Facebook的Redex即是这一领域的著名开源框架 。 优酷并没有使用复杂的排布优化策略 , 而是自定义了简单的Dex合并能力 , 获得了约2MB左右的包瘦身收益 。【代码】字节码指令精简 。 准确的说这并不是一项瘦身技术 , 而是一类瘦身技术的集合 。 通过更精细的字节码上下文分析 , 以删除、合并、转换等方式精简指令序列 , 从而达到瘦身目的 , 例如删除冗余赋值指令(值与类型默认值一致)、access$xxx方法消除(修改private方法为public , 避免access方法生成)、常量/短方法内联等等 。 不知道大家是不是会有个疑问 , proguard/R8没有进行这些处理吗?这些“民间”自定义的字节码指令精简方案 , 可以看作是对前者的一种“极致性”扩展 , 因为前者在进行字节码优化时对正确性的要求极高 , 如果有些优化策略存在风险 , 或者违背原代码设计意图(比如修改private方法为public) , 那么就不会应用 。 当需要使用自定义的字节码指令精简之前 , 建议先把proguard/R8各种优化配置选项研究透彻并充分应用 , 可能你会发现通过配置就可以实现同样效果 , 并且处理过程更稳定、高效、可信 , 如果不得不走到需要进行自定义处理的境地 , 也一定要谨慎使用 。【资源】无用资源裁剪 。 ShrinkResources[3


推荐阅读