NIO App 包体积优化实践 – 资源优化分享

有时候,当你回头看看自己走了多远时,才能更好的衡量自己的进步。

源码仓库:github.com/Omooo/Laven…

PPT:gamma.app/public/App-…

引言

优化目的

首先我们需要了解,优化包体积大小的目的是什么?总结有以下几点:

  1. 下载转化率

    以 Google Play 的 数据统计 每减少 10M 大小,有 1.5% 的下载转化率提升。

  2. 推广成本

    包体积对渠道推广成本和厂商预装单价会有比较大的影响。

  3. 应用性能

    具体表现在安装时间、运行内存、ROM 空间。应用在首次安装启动时,过多的 odex 文件编译会严重影响冷启动时间;应用在运行时,Resource 资源、Library 以及 Dex 类加载这些都会占用不少内存;安装包在解压时,占用 ROM 空间可能会翻倍,对 ROM 空间占用也会有一定压力。

  4. 编译耗时

    过多的 dex 和 res 会影响编译时长。

现状梳理

在最近几个版本中,我们针对资源做了一点微小的工作。之所以选择资源这一块,是因为资源占比是最大的,以 v5.10.0 版本的数据来看:

类别 大小(单位:M) 占比
res 65.9 40.0%
assets 9.4 5.7%
resources.arsc 10.4 6.3%

资源总计占比达到了 52%,所以我们就决定从资源入手。

当然更主要的原因是 :

  1. 较简单、不易出问题

    相较于 dex 和 so 的优化,资源的优化手段较为简单,能删则删,不能删就压缩,没有什么黑科技的手段,也就不易出问题。dex 一般涉及到字节码优化,比如删除冗余的赋值指令、dex 重分包等等;so 的优化需要良好的基建配套支持,这些都很容易出线上问题,我们目前没有完善的灰度流程和热修复方案,所以一旦出了问题就直接尬住了。

  2. 易量化、所见即所得

    包体积的优化收益很容易量化,前后一对比就知道了,不会受其他环境影响。其他的比如启动优化,线下测试可能收益不错,上线了之后就不及预期了,线上环境很复杂,可能需要考虑到很多控制变量。

所以,前期还是先找软柿子捏。

手段

版本 优化点 大小(单位:M) 优化效果(单位:M) 说明
v5.11 / 168.8 / 基线版本
v5.12 png 转 webp 129.5 -39.53 输出的报告是减少了 51M,差值的 12M 是 AAPT2 自带的 crunchPngs 压缩 PNG 导致
v5.14 删除无用 assets 资源 148.6 -0.9 删除了 33 个无用 assets 资源,减少占比 -10%
v5.16 APK 增量分析 / / 防裂化手段

图片压缩

目前 NIO APP minSdk = 24,可以全量使用 webp,根据 官方文档 的数据,无损的 webp 图片比 png 可减少 26% 的大小。具体的创建 webp 操作可见官方文档:创建 WebP 图片

这种方式存在两个问题:

  1. 需要推动 AAR 的各个业务方去转化
  2. 无法处理第三方库里面的图片
实现原理

一劳永逸的做法是在打 release 包时,自动转化所有的 png 图片,核心操作步骤是:

  1. BaseVariant#getAllRawAndroidResources() 可获取所有的资源文件,过滤出待转化的 png 图片
  2. 使用 cwebp 工具转化所有的图片
注意事项

需要注意根据 Google Play 图标设计规范,需要将启动图标加入豁免,还有就是过滤掉 .9 图。

无用 assets 资源删除

shrinkResources 无法处理 assets 资源,如何找出未被使用的 assets 资源呢?

实现原理

实现原理类似于 Matrix#UnusedAssetsTask,该方案的是:搜索 smali 文件中引用字符串常量的指令,判断引用的字符串常量是否某个 assets 文件的名称。而我们的做法是:

所以核心问题就变成了:如何获取所有的引用字符串?

我们的做法是:在 assembleRelease 后解析 resources.txt 文件,匹配出所有的引用字符串,所以该任务是依赖于 assembleReleaseTask。

其实思路都是来源于 AGP 的 ResourceUsageAnalyzer.java 实现:

注意事项

有以下情况 assets 资源需要加入白名单:

  1. Dead Code 引用的 assets 资源(比如 XxLottieCheckBox 用到了 xx_checkbox_ok.json)
  2. Layout 文件中引用的 assets 资源,典型的就是 lottie 资源文件
  3. 其他资源文件(比如 raw 类型的文件)引用的 assets 资源

APK 增量分析

APK 每个版本都有近 10M 的增量,但是无法知道这些增量来源于哪里?不利于 App 包体积良性增长。

如果在某个业务中引用了较大的资源文件,如何能够及时发现呢?

那么,这正是这个任务所要解决的问题。我们在 jenkins 上配置了这个任务,jenkins 打包后会生成一个分析报告:

解析 APK

这一步主要的目标是,解析 Apk 生成文件列表。它涉及解析依赖、解 dex 文件、解混淆。

其中比较麻烦的是资源的混淆问题。

不同于类的反混淆,资源混淆是没有默认生成的 mapping 文件的。那如何去解决这个问题呢?

我们首先想到的是使用 md5 对比 resources-release.ap_ 和 resources-release-optimize.ap_ 文件,这可以解决绝大部分的资源混淆问题。因为 OptimizeResourcesTask 默认只做 “–shorten-resource-paths” 处理,即缩短资源路径,不会对文件内容处理,所以可以通过对比混淆前后资源文件的 md5 值,得到混淆前后资源名的映射。但有一种情况例外,即资源未被使用,在 ShrinkResourcesTask 时被重写成了空文件。

这种情况下,就无法使用 md5 对比了。那还有什么办法呢?

其实 AAPT2 提供了生成资源 mapping 文件的命令行参数,见:AAPT2 – 优化选项,但是该参数,没法通过 gradle.properties 或 aaptOptions 来指定,所以我们最终解决方案就是,使用 AAPT2 对 resources-release.ap_ 文件进行再处理一次,获取到 mapping 文件即可。混淆规则可见:ResourcePathShortener

增量分析

增量分析这一步的目标是,对比上一个版本的文件列表,输出差异。那上一个版本的文件列表是如何存储的呢?

其实是存储在了 {projectDir}/lavender-plugin/apk/previous.json 下,在第一次运行该任务时,就会把当前版本的 Apk 文件列表存储至此。

可视化

如何高效的推进?它涉及两个问题:

  1. 如何定位问题所属业务方?
  2. 报告有无更加友好的方式?
映射关系

在找到问题所在后,如何定位到所属业务方呢?其实是我们维护了一个 AAR artifact id 和负责人之间的映射关系,类似如下:

报告形式

json 报告可视化太差了,有无友好的方式?当然有的我们用 Kotlin/JS 写了一个 html 模版:

产物分析

以上获取所有图片资源、获取 assets 资源、获取 APK 文件等等,都是一句话带过了。其实这些都是基于产物分析,也就是在 Gradle 构建阶段,这些产物都是可以拿到的。核心的代码如下:

// ArtifactType: AAR、APK、CLASSES、MANIFEST、ANDROID_RES、ASSETS
internal fun ApplicationVariantImpl.getArtifactFiles(artifactType: AndroidArtifacts.ArtifactType): List<File> {
    return variantData.variantDependencies.getArtifactCollection(
        AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,
        AndroidArtifacts.ArtifactScope.ALL,
        artifactType
    ).artifacts.map { artifact ->
        artifact.file
    }
}

所以,我们写的很多 Task 都是基于此的。

唯一需要注意的是,Task 之间的依赖关系。比如,系统的 mergeResTask 需要依赖我们自定义的图片压缩 Task,如果我们要列出所有 AAR 里面的权限信息,那么我们写的自定义 Task 就要依赖系统的 mergeManifestTask。

致谢

  1. github.com/didi/booste…
  2. github.com/spotify/rul…
  3. chat.openai.com/chat

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYa1EfTU' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片