当前位置: 首页 > 后端技术 > Java

百度APP安卓包体积优化实践(一)概述

时间:2023-04-01 17:12:19 Java

01前言百度APP已经有了基本的包体积优化机制、约束和意识,但是作为一艘巨轮APP,业务的高速迭代还是不可避免的造成了封装尺寸增加。量的爆发式增长。包大小直接或间接影响下载转化率、安装时间、运行内存、磁盘空间等重要指标,因此需要投入精力消除累积的劣势,探索更深层次的体积优化项。根据GoogleStore内部数据,APK大小每减少10M,下载转化率平均可提升~1.5%,如下图所示:图1GoogleStore应用转化率提升/10M[1]

优化Android包大小的方法有很多,比如业务剪裁、插件化、混合开发、资源发布后等,本系列文章主要针对与业务无关的、集成在APK中的内容的体量优化,如Dex优化、资源优化、SO优化等,我们称之为基础机制优化。包卷基本机制的优化实践将以系列文章的形式呈现,主要包括以下几个部分:心路历程、Dex行数优化完整方案、资源优化实践与探索、Dex优化实践与探索、so优化探索、其他优化经验和总结。本文描述了优化百度APP包体积基本机制的心路历程,包括持续引导的基本思路、优化对象分析、现有优化工具的学习、最终的体积优化项。02基本思路2.1分而治之我们的优化对象不仅仅是APK的最终产品,还有APK中的内容。这些内容的音量优化思路和方法各不相同。2.2可持续优化好的优化机制不仅对现在有效,对未来也有效。例如,从源代码仓库中删除当前的死代码是一种一次性的库存优化操作,而编译器的DCE机制(DeadCodeElimination)可以对以后产生的死代码继续生效。从长远看,应优先建立后者,然后倒推前者的实施。2.3站在前人的肩膀上Packsize优化并不是一个新鲜话题,Android官方和开发者都在持续致力于优化size。不提倡重新发明轮子,但针对不同的应用场景,尤其是巨轮APP,应该有体积优化的定制方案。2.4厘清成本,权衡取舍根据热力学第一定律,收益不会凭空产生,一定会伴随成本,如人力投入、编译时间增加、适配难度增加等.成本明确后,我们就可以决定是否、何时、如何做一个优化项。2.5约束和意识除了自动优化机制外,还需要具备自动增量约束。同时,应从源头上提高开发者的体量优化意识,多管齐下达到最佳效果。03APK结构分析接下来我们简单分析一下APK中的各个组成部分,以及APK为ZIP的标准结构。3.1APK内容分析图2APK结构

classes.dexAPK中可能包含一个或多个classes.dex文件,应用中的Java/Kotlin源码最终会转化为dalvik字节码方法存在于classes.dex文件中。resources.arsc这个文件是包含配置信息的资源查找表,充当代码和资源之间的链接。Dex文件中的R.class只包含资源id,AssetManager会根据id在arsc表中查询与当前设备信息最匹配的资源文件路径(或资源内容)。res/包含源码项目res目录下除values以外的资源文件,这些文件的路径会同时反映在resources.arsc中。lib/本机库。即源码项目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.2ZIP结构分析图3ZIP标准结构图

压缩源文件信息本地文件头:描述源文件信息。文件数据:源文件数据。数据描述符:检查压缩前后的代码和大小。中央目录区中央目录记录了ZIP目录结构。每个文件头对应一个源文件,描述文件的相关信息。Endofcentraldirectoryrecord标志着ZIP包的结束,包括ZIP包和中心目录的简要信息。04现有优化工具介绍对于开发初期的应用,体积优化的优先级较低,直接使用以下体积优化工具是性价比最高的选择。百度APP也对比借鉴了以下工具,从中衍生出新的、定制化的优化需求。4.1ProGuard在AGP3.3之前,ProGuard作为官方的尺寸优化工具,负责对编译后的class文件进行缩减和混淆,优化结果交给Dx/D8转化为Dex产品。图4Proguard处理对象及其功能示意图[9]

ProGuard的优化操作主要包括:Reduction:安全地移除无用的类、方法、字段和属性。混淆:缩短类名和成员名。优化:指令级优化,合并重复指令,清理无用指令,提高指令执行效率。4.2R8AGP3.3之后,官方开始推荐使用R8。R8和ProGuard不仅仅是简单的替换关系,它还集成了desugar和D8,大大提高了构建效率。图5R8处理对象和函数示意图[9]R8与之前的ProGuard规则基本兼容,但还是有一些区别(applymapping、行号处理、Kotlin元数据处理、无用判断等)。在R8不再考虑兼容性问题之后,两者之间会衍生出越来越多的差异。建议定期关注,多多学习。丨Jack&Jill小插曲:Jack&Jill工具,2015年正式推广了一段时间,甚至包括javac,也算是真正的完结-到结束编译。不过与Javac的生态相比,Jack的性能差距太大,最终官方出于成本考虑弃坑。4.3AndResGuardAndResGuard是微信推出的一款资源优化工具。它的基本思想类似于混淆器中的混淆,体积优化是它的额外好处,它还提供压缩和加密等选项。4.4ByteXByteX是Byte开源的一套Java字节码检测工具。目前主要包括优化和检查工作,部分子项最终会带来体量收益。包括R类内联、去除调试信息、访问方法内联等。4.5BoosterBooster是滴滴开源的一套质量优化框架,包括资源文件压缩、资源积.ap\_压缩、冗余等专项优化项目resourcesremoval,Rclassinlining,DataBindingBRinlining等。4.6AGPAndroidGradlePlugin(AGP)包含多个体积优化任务,提供了很多优化配置项,其中大部分已经在APK中标配。通常,我们的优化任务将取决于这些任务的执行。如果自定义优化与现有任务不兼容,则需要关闭或挂接这些任务。下面按照编译顺序简单介绍几个优化任务和配置:OptimizeResourcesAGP4.2+新增资源优化任务,目前只实现了资源文件路径的缩短,默认开启,可以通过android关闭.enableResourceOptimizations。StripSymbolsNDK会使用llvm-strip去除原生库中不需要的符号,这部分优化工作也可以在so编译时完成。MinifyWithR8/ProGuard是使用R8还是ProGuard来实现代码优化,这里不再赘述。ShrinkResources由ShrinkResources开关控制,启用它的前提是启用minifyEnable。它的作用是将没有被引用的资源文件替换成一个小格式文件(还有一个footprint,同时保留资源入口,所以resources.arsc的体积不会减少),可以通过res访问/raw/keep.xml文件配置shrinkMode和白名单。打包时的PackageOptions选项,包括filterexclude,只pickFirst打包同一个文件,所有packagemerge,所以优化豁免doNotStrip。拆分分包/过滤策略,配置项包括ABI、资源配置(语言、分辨率等)。05百度APP优化项概述5.1Dex优化百度APP实现Dex大小优化项可以分为两类:源码编译时的优化;APK打包时Dex文件的优化。两者的区别主要在于优化对象不同,因此基于不同的优化工具来实现。前者基于Java字节码工具(如ASM)实现,后者基于Dex字节码工具(如Titan-Dex[10])实现。丨Titan-DexTitan-Dex是百度开源的Android操作系统操纵框架Dalvik(ART)字节码(bytecode)格式,可以二进制格式修改现有的类,或者动态生成新的类。百度Titan-hotfix工具就是基于这个框架实现的。R类优化工程组件越多,R类占用越大,资源依赖转移未关闭时情况越严重。我们在编译时将代码中调用R.type.name的地方全部替换为对应的id常量,最终R.class会被R8/ProGuard清理为无用类。行数优化Dex中的debug区占5-10%的大小,但它最大的作用是在分析crashstack时定位。通过移除ProGuard规则-keepattributesSourceFile,LineNumberTable可以完全移除此区域。我们选择在指令级完成调试信息的映射和复用,同时链接百度性能平台(目前仅供公司内部使用,功能可比拟腾讯bugly)完成对crashstack,既优化了大小,又不影响stackanalyze。注解优化Dex中的注解分为三种类型:Build、Runtime和System。Build和Runtime对应ProGuard规则-keepattributes*Annotation*_,优化的System注解根据具体类型对应-keepattributesInnerClasses、Signature、EnclosingMethod_。与行号一样,可以通过删除这些规则来实现一刀切的优化。但是由于我们接入的第三方组件自带这些ProGuard规则,需要保留一些类型的System注解,所以我们选择以后处理的方式处理Dex文件,根据目标注解完成去除Dex字节码工具。5.2资源优化资源优化对象分为两类,一类是资源查询表resources.arsc,一些优化操作会涉及到res/和R文件的修改,但本质都是从resources.arsc开始;另一个是原始资源文件,包括res/和assets/。在介绍优化项之前,先看看网上最经典的resources.arsc结构图(来源:CSDN社区):图6resources.arsc结构图

在实际应用中,资源同名化,默认情况下,我们通过资源id来搜索资源内容,资源名称的使用频率很低。仅限于通过资源名取资源id和通过资源id获取资源名这两种情况。所以资源项名称字符串池所占用的空间就是我们的优化对象。极度优化的结果是这个池子里只存了一个字符串,所有ResTable_entry的资源项名称索引都指向了这个池子里唯一的一个字符串,也就是所有资源的名字都变得一样了。在实际场景中,我们会有豁免和降级混淆的需求,比如通过资源名称反向查询资源id。资源文件路径优化类似于AndResGuard中的资源路径混淆效果,尽可能缩短资源文件的路径长度,从而减小ResTable\_entry的值大小。我们将路径Hash值转化为碰撞最少的位数作为最终的混淆结果。优点是不用applymapping混淆结果基本固定。此外,我们还积极删除了大多数文件的后缀。资源配置优化包括arsc中资源id的偏移量信息,系统通过偏移量定位arsc中的资源。因此,必须在图7中的空白区域预留4个字节的占用,以满足偏移量查询方式。我们正在优化这部分,目的是通过优化不必要的配置来达到减少对齐空间的目的。图7resources.arsc空白空间占用图

图片压缩由于webp格式受限于minsdkversion18,目前我们还在优化png图片的压缩,使用工具包括TinyPng和ImageOptim。除了输出阶段的压缩之外,还将有一个用于压缩检查的后管道。无用的资源清理正如4.5中提到的,ShrinkResources实际上并没有删除未引用的资源文件。但是我们可以获取缩水后的资源列表,然后使用资源优化工具进行真正的删除。丨NewShrinkResource2022.1发布的AGPv7.1.0更新了资源缩水功能,增加了一个实验性的选项android.experimental.enableNewResourceShrinker.preciseShrinking。将此选项设置为true后,ShrinkResources会彻底清除不用的文件资源和值资源,但arsc中仍然会对这些资源进行填充。arsc压缩资源。arsc具有很高的压缩量增益,但压缩它会影响启动速度和内存指标。具体原因是:系统加载arsc文件时,如果arsc文件没有被压缩,可以使用mmap进行内存映射;如果arsc文件是压缩文件,需要解压后读入RAM缓冲区,会增加内存占用。它还会减慢启动速度。在业界大部分压缩arsc的情况下,百度APP综合考虑没有压缩arsc文件。巧合的是,出于同样的考虑,官方从Android11开始强制要求resources.arsc不可压缩并保持4位对齐,否则会直接安装失败。图8Android11强调resources.arsc的压缩对齐问题

5.3ZIP优化压缩目前我们使用7z和zopfli[11]压缩算法,后者在压缩率和压缩率上有明显提升压缩时间,稳定性还在验证中。在采用新的压缩算法时,需要特别注意两点。一是不压缩resources.arsc;二是注意压缩、对齐、签名操作的先后顺序。文件路径优化如3.2ZIP结构分析章节所示,APK中与文件路径长度相关的三个卷:META-INF/,压缩源文件数据区的本地文件头,文件头在中央目录区,和资源文件路径的优化效果一样会体现在这里。同样,将文件路径控制在assets/下也能带来体积上的好处。5.4其他夜间模式优化目前百度APP的夜间资源为APK包。之前的实现方式是在主包中保持相同的资源名称,通过反射查询对应的id。现在把id改成一样,这样就避免了反射查询的耗时,而且5.2章节的资源优化也可以应用到资源APK上,进一步减小体积。混淆规则ProGuard/R8提供了多种规则来免除代码优化操作,如果使用不当,可能会造成体积浪费。未来我们计划制定一套详细的ProGuard规则使用规范,对各个组件的ProGuard规则进行验证,比如keep规则不允许超出本组件包名范围,包级keep是不允许。体积流水线主要包括仓库体积约束流水线、二进制组件体积检查流水线、APK组成体积分析流水线,分阶段进行约束和分析。目前,百度APP已经搭建了APK流量监控管道。每当代码合并到主线并触发编译打包时,它会立即分析编译后的产品APK的体积,并与之前的编译产品进行比较。可以立即发现异常的体积增长。06小结第5章介绍的优化项在8月21日至2月22日分批上线,期间业务仍在高速迭代。虽然有体积监控流水线,但包体积难免会变大。由于优化机制非常底层,需要充分的离线测试和线上小流量灰度验证稳定性,才能正式上线。百度APP上线前后包大小对比/灰度如下:——————END——————参考资料:[1]包大小和安装转化率https://medium.com/googleplay...[2]ZIP格式https://pkware.cachefly.net/w...[3]ProGuardhttps://www.guardsquare.com/p...[4]R8https://r8.googlesource.com/r8[5]ProGuard与R8的比较https://www.guardsquare.com/b...[6]AndResGuardhttps://github.com/shwenzhang...[7]ByteXhttps://github.com/bytedance/...[8]Boosterhttps://github.com/didi/booster[9]AGPhttps://developer.android.com...[10]Titan-Dexhttps://github.com/baidu/tita...[11]zopflihttps://en.wikipedia.org/wiki...推荐阅读:百度APPiOS端内存优化实践-基于AE视频渲染技术的大内存监控方案百家号探秘百度工程师教你玩转设计模式(observerpattern)Linux透明巨页机制引入云上超高效大规模集群实践!Swagger-Yapi之秘境百度直播iOSSDK平台输出改造