前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入探索 Android 包瘦身(下)——终篇

深入探索 Android 包瘦身(下)——终篇

作者头像
陈宇明
发布2020-12-16 15:52:12
1.9K0
发布2020-12-16 15:52:12
举报
文章被收录于专栏:设计模式设计模式

作者:jsonchao 链接:https://juejin.im/post/5e7ad1c0e51d450edc0cf053

复习上篇:《深入探索 Android 包瘦身(上)

中篇:《深入探索 Android 包瘦身(中)》

四、So 瘦身方案探索

对于主要由 C/C++ 实现的 Native Library 而言,常规的优化方式就是 去除 Debug 信息,使用 C++_shared 等等。下面,对于 So 瘦身,我们看看还有哪些方案。

1、So 移除方案

SoAndroid 上的动态链接库,在我们 Android 应用开发过程中,有时候 Java 代码不能满足需求,比如一些 加解密算法或者音视频编解码功能,这个时候就必须要通过 C 或者是 C++ 来实现,之后生成 So 文件提供给 Java 层来调用,在生成 So 文件的时候就需要考虑生成市面上不同手机 CPU 架构的文件。目前,Android 一共 支持7种不同类型的 CPU 架构,比如常见的 armeabi、armeabi-v7a、X86 等等。理论上来说,对应架构的 CPU 它的执行效率是最高的,但是这样会导致 在 lib 目录下会多存放了各个平台架构的 So 文件,所以 App 的体积自然也就更大了。

因此,我们就需要对 lib 目录进行缩减,我们 在 build.gradle 中配置这个 abiFiliters 去设置 App 支持的 So 架构,其配置代码如下所示:

代码语言:javascript
复制


defaultConfig {
    ndk {
        abiFilters "armeabi"
    }
}

一般情况下,应用都不需要用到 neon 指令集,我们只需留下 armeabi 目录就可以了。因为 armeabi 目录下的 So 可以兼容别的平台上的 So,相当于是一个万金油,都可以使用。但是,这样 别的平台使用时性能上就会有所损耗,失去了对特定平台的优化

2、So 移除方案优化版

上面我们说到了想要完美支持所有类型的设备代价太大,那么,我们能不能采取一个 折中的方案,就是 对于性能敏感的模块,它使用到的 So,我们都放在 armeabi 目录当中随着 Apk 发出去,然后我们在代码中来判断一下当前设备所属的 CPU 类型,根据不同设备 CPU 类型来加载对应架构的 So 文件。这里我们举一个小栗子,比如说我们 armeabi 目录下也加上了 armeabi-v7 对应的 So,然后我们就可以在代码当中做判断,如果你是 armeabi-v7 架构的手机,那我们就直接加载这个 So,以此达到最佳的性能,这样包体积其实也没有增加多少,同时也实现了高性能的目的,比如 微信和腾讯视频 App 里面就使用了这种方式,如下图所示:

看到上图中的 libimagepipeline_x86.so,下面我们就以这个 so 为例来写写加载它的伪代码,如下所示:

代码语言:javascript
复制
String abi = "";
// 获取当前手机的CPU架构类型
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
    abi = Buildl.CPU_ABI;
} else {
    abi = Build.SUPPORTED_ABIS[0];
}

if (TextUtils.equals(abi, "x86")) {
    // 加载特定平台的So
    
} else {
    // 正常加载
    
}

接下来,我们再了解下 So 优化当中别的优化方式。

3、使用 XZ Utils 对 Native Library 进行压缩

Native LibraryDex 一样,也可以使用 XZ Utils 进行压缩,对于 Native Library 的压缩,我们 只需要去加载启动过程相关的 Library,而其它的都可以在应用首次启动时进行解压,并且,压缩效果与 Dex 压缩的效果是相似的

此外,关于 Nativie Library 压缩之后的解压,我们也可以使用 Facebook 的 so 加载库 SoLoader,它 能够解压应用的 Native Library 并能递归地加载在 Android 平台上不支持的依赖项。由于这套方案对启动时间的影响比较大,所以先把它压箱底下吧。

4、对 Native Library 进行合并

在 Android 4.3(API 17) 之前,单个进程加载的 SO 数量是有限制的,在 Google 的 linker.cpp 源码中有很明显的定义,如下图所示:

为了解决这个问题,FaceBook 写了一个 合并Native Library的demo,我们可以 按照自身 App 的 so 情况来配置需要合并哪些对象。由于合并共享对象(即 .so 文件)在原先的构建流程中是无法实现的,因此 FaceBook 更改了链接库的方式,并把它集成到了构建系统 Buck 中。该功能允许每个应用程序指定应合并的 .so 库,从而避免意外引入不必要的依赖关系。然后,Buck 负责为每个合并的 .so 库收集所有对象(文件),并将它们与适当的依赖项链接在一起。

5、删除 Native Library 中无用的导出 symbol

我们可以去 分析代码中的 JNI 方法以及不同 Library 库的方法调用,然后找出无用的 symbol 并删除,这样 Linker 在编译的时候也会把 symbol 对应的无用代码给删除。在 Buck 有 NativeRelinker 这个类,它就实现了这个功能,其 类似于 Native Library 的 ProGuard Shrinking 功能

至此,可以看到,FaceBook 出品的 Buck 同 ReDex 一样,里面的功能都十分强大,Buck 除了实现 Library Merge 和 Relinker 功能之外,还实现了三大功能,如下所示:

  • 1)、多语言拆分
  • 2)、分包支持
  • 3)、ReDex 支持

如果有相应需求或对 Buck 感兴趣的同学可以去看看它们的实现源码。

6、So 动态下载

我们可以 将部分 So 文件使用动态下发的形式进行加载。也就是在业务代码操作之前,我们可以先从服务器下载下来 So,接下来再使用,这样包体积肯定会减少不小。但是,如果要把这项技术 稳定落地到实际生产项目中需要解决一些问题,具体的 so 动态化关键技术点和需要避免的坑可以参见 动态下发 so 库在 Android APK 安装包瘦身方面的应用,这里就不多赘述了。

五、其它优化方案

1、插件化

我们可以使用插件化的手段 对代码结构进行调整,如果我们 App 当中的每一个功能都是一个插件,并且都是可以从服务器下发下来的,那 App 的包体积肯定会小很多。插件化相关的知识非常多而且不属于我们的重点,并且,插件化严格来说属于 基础架构研发 这块的知识,掌握它是成为 Android 架构师的必经之路,关于 Android 架构师的学习路线 可以参考 Awesome-Android-Architecture,预计今年会完成部分学习内容,敬请期待。

2、业务梳理

我们需要 回顾过去的业务,合理地去 评估并删除无用或者低价值的业务

3、转变开发模式

如果所有的功能都不能移除,那就可能需要去转变开发模式,比如可以更多地 采用 H5、小程序 这样开发模式。

六、包体积监控

对于应用包体积的监控,也应该和内存监控一样,去作为正式版本的发布流程中的一环,并且应该 尽量地去实现自动化与平台化。(这里建议 任何大于 100kb 的功能都需要审批,特别是需要引入第三方库时,更应该慎重)

1、包体积监控的纬度

包体积的监控,主要可以从如下 三个纬度 来进行:

  • 1)、大小监控:通常是记录当前版本与上一个或几个版本直接的变化情况,如果当前版本体积增长较大,则需要分析具体原因,看是否有优化空间
  • 2)、依赖监控:包括J ar、aar 依赖
  • 3)、规则监控:我们可以把包体积的监控抽象为无用资源、大文件、重复文件、R 文件等这些规则

包体积的 大小监控依赖监控 都很容易实现,而要实现 规则监控 却得花不少功夫,幸运的是 Matrix 中的 ApkChecker 就实现了包体积的规则监控,其 使用文档与实现原理 微信团队已经写得很清楚了,这里就不再一一赘述,有兴趣的同学可以去研究下。

七、瘦身优化常见问题

瘦身优化是性能优化当中不那么重要的一个分支,不过对于处于稳定运营期的产品会比较有帮助。下面我们就来看看对于瘦身优化有哪些常见问题。

1、怎么降低 Apk 包大小?

我们在回答的时候要注意一些 可操作的干货,同时注意结合你的 项目周期。主要可以从以下 三点 来回答:

  • 1)、代码:Proguard、统一三方库、无用代码删除
  • 2)、资源:无用资源删除、资源混淆
  • 3)、So:只保留 Armeabi、更优方案

在项目初期,我们一直在不断地加功能,加入了很多的代码、资源,同时呢,也没有相应的规范,所以说,UI 同学给我们很多 UI 图的时候,都是没有经过压缩的图片,长期累积就会导致我们的包体积越来越大。到了项目稳定期的时候,我们对各种运营数据进行考核,发现 APK 的包大小影响了用户下载的意愿,于是我们就着手做包体积的优化,我们采用的是 Android Studio 自带的 Analyze APK 来做的包体积分析,主要就是做了代码、资源、So 等三个方面的重点优化。

首先,针对于代码瘦身,第一点,我们首先 使用 Proguard 工具进行了混淆,它将程序代码转换为功能相同,但是不容易理解的形式。比如说将一个很长的类转换为字母 a,同时,这样做还有一个好处,就是让代码更加安全了。第二点呢,我们将项目中使用到的一些 第三方库进行了统一,比如说图片库、网络库、数据库等,不允许项目中出现功能相同,但是却实现不一样的库。同时也做了 规范,之后引入的三方库,需要去考量它的大小、方法数等,而且呢,如果只是需要一个很大库的一个小功能,那我们就修改源码,只引入部分代码即可。第三点,我们将项目中的 无用代码进行了删减,我们使用了 AOP 的方式统计到了哪些 Activity 以及 fragment 在真实的场景下没有用户使用,这样你就可以删除掉了。对于那些不是 Activity 或者是 Fragment 的类,我们切了很多类的构造函数,这样你就可以统计出来这些类在线上有没有真正被调用到。但是,对于代码的瘦身效果,实际上不是很明显

接下来,我们做了资源的瘦身。首先,我们 移除了项目当中冗余的资源文件,这一点在项目当中一定会遇到。然后,我们做了 资源图片的压缩,UI 同学给我们资源图片的时候,需要确认已经是压缩过的图片,同时,我们还会做一个 兜底策略,在打包的时候,如果图片没有被压缩过,那我们就会再来压缩一遍,这个效果就非常的明显。对于资源,我们还做了 资源的混淆,也就是将冗余的资源名称换成简短的名字,资源压缩的效果要比代码瘦身的效果要好的多

最后,我们做了 So 的瘦身。首先,我们只保留了 armeabi 这个目录,它可以 兼容别的 CPU 架构,这点的优化效果非常的明显。移除了对别的架构适配 So 之后,我们还做了另外一个处理,对于项目当中使用到的视频模块的 So,它对性能要求非常高,所以我们采用了另外一种方式,我们将所有这个模块下的 So 都放到了 armeabi 这个目录下,然后在代码中做判断,如果是别的 CPU 架构,那我们就加载对应 CPU 架构的 So 文件即可。这样即减少了包体积,同时又达到了性能最佳。最后,通过实践可以看出 So瘦身的效果一般是最好的

2、Apk 瘦身如何实现长效治理?

主要可以从以下 两个方面 来进行回答:

  • 1)、发版之前与上个版本包体积对比,超过阈值则必须优化
  • 2)、推进插件化架构改进

在大型项目中,最好的方式就是 结合 CI,每个开发同学 在往主干合入代码的时候需要经过一次预编译,这个预编译出来的包对比主干打出来的包大小,如果超过阈值则不允许合入,需要提交代码的同学自己去优化去提交的代码。此外,针对项目的 架构,我们可以做 插件化的改造,将每一个功能模块都改造成插件,以插件的形式来支持动态下发,这样应用的包体积就可以从根本上变小了

八、总结

在本篇文章中,我们主要从以下 七个方面 讲解了 Android 包体积优化相关的知识:

  • 1)、瘦身优化及 Apk 分析方案:瘦身优势、APK 组成、APK 分析
  • 2)、代码瘦身方案探索:Dex 探秘、ProGuard、D8 与 R8 优化、去除 debug 信息与行号信息、Dex 分包优化、使用 XZ Utils 进行 Dex 压缩、三方库处理、移除无用代码、避免产生 Java access 方法、利用 ByteX Gradle 插件平台中的代码优化插件
  • 3)、资源瘦身方案探索:冗余资源优化、重复资源优化、图片压缩、使用针对性的图片格式、资源混淆、R Field 的内联优化、资源合并方案、资源文件最少化配置、尽量每张图片只保留一份、资源在线化、统一应用风格
  • 4)、So 瘦身方案探索:So 移除方案、So 移除方案优化版、使用 XZ Utils 对 Native Library 进行压缩、对 Native Library 进行合并、删除 Native Library 中无用的导出 symbol、So 动态下载
  • 5)、其它优化方案:插件化、业务梳理、转变开发模式
  • 6)、包体积监控
  • 7)、瘦身优化常见问题

如果要想对包体积做更深入的优化,我们就必须对 APK 组成,Dex、So 动态库以及 Resource 文件格式,还有 APK 的编译流程 有深入地了解,这样我们才能有 足够的内功素养 去实现包体积的深度优化。此外,在做性能优化过程中,为了提升研发效率,降低研发成本,我渐渐发现 AOP 编译插桩、Gradle 自动化构建 的知识越来越重要;并且,一旦涉及 Native 层甚至 Android 内核层的深度优化时,就越发感觉到功力不足。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码个蛋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、So 移除方案
  • 2、So 移除方案优化版
  • 3、使用 XZ Utils 对 Native Library 进行压缩
  • 4、对 Native Library 进行合并
  • 5、删除 Native Library 中无用的导出 symbol
  • 6、So 动态下载
  • 1、插件化
  • 2、业务梳理
  • 3、转变开发模式
  • 1、包体积监控的纬度
  • 1、怎么降低 Apk 包大小?
  • 2、Apk 瘦身如何实现长效治理?
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档