作者:来自 vivo 互联网大前端团队- Ke Jie

介绍 App 包体积优化的必要性,游戏中心 App 在实际优化过程中的有效措施,包括一些优化建议以及优化思路。

一、包体积优化的必要性

安装包大小与下载转化率的关系大致是成反比的,即安装包越大,下载转换率就越差。Google 曾在 2019 的谷歌大会上给出过一个统计结论,包体积体大小每上升 6MB,应用下载转化率就会下降 1%,在不同地区的表现可能会有所差异。

APK 减少 10MB,在不同国家转化率增长

(注:数据来自于 googleplaydev:Shrinking APKs, growing installs

二、游戏中心 APK 组成

APK 包含以下目录:

  • META-INF/:包含 CERT.SF 、CERT.RSA 签名文件、MANIFEST.MF 清单文件。

  • assets/:包含应用的资源。

  • res/:包含未编译到 resources.arsc 中的资源。

  • lib/:支持对应 CPU 架构的 so 文件。

  • resources.arsc:资源索引文件。

  • classes.dex:可以理解的 dex 文件就是项目代码编译为 class 文件后的集合。

  • AndroidManifest.xml:包含核心 Android 清单文件。此文件列出了应用的名称、版本、访问权限和引用的库文件。

发现占包体积比较大的主要是 lib、res、assets、resources 这几个部分,优化主要也从这几个方面入手。

三、包体积检测工具

Matrix-ApkChecker 作为 Matrix 系统的一部分,是针对 Android 安装包的分析检测工具,根据一系列设定好的规则检测 APK 是否存在特定的问题,并输出较为详细的检测结果报告,用于分析排查问题以及版本追踪。

配置游戏中心的 Json,主要检测 APK 是否经过了资源混淆、不含 Alpha 通道的 PNG 文件、未经压缩的文件类型、冗余的文件、无用资源等信息。

对于生成的检测文件进行分析,可以优化不少体积。

工具 Matrix Apkcheck 介绍:https://github.com/Tencent/matrix/wiki/Matrix-Android-ApkChecker

四、包体积优化措施

4.1 不含 Alpha 通道的 PNG 大图

项目中存在较多这种类型的图,可以替换为 JPG 或者 WebP 图,能减少不少体积。

4.2 代码做减法

随着业务的迭代,很多业务场景是不会再使用了,涉及到相关的资源和类文件都可以删除掉,相应的 APK 中 res 和 dex 都会相应减少。游戏中心这次去掉了些经过迭代后没有使用的业务场景和资源。

4.3 资源文件最少化配置

针对内销的项目,本地的 string.xml 或者 SDK 中的 string.xml 文件中的多语言,是根本用不到的。这部分资源可以优化掉,能减少不少体积。

在 APP 的 build.gradle 中下添加 resConfigs "zh-rCN", "zh-rTW", "zh-rHK"。这样配置不影响英文、中文、中国台湾繁体、中国香港繁体语言的展示。

资源文件最少化配置前

资源文件最少化配置后

4.4 配置资源优化

很多项目为了适配各种尺寸的分辨率,同一份资源可能在不同的分辨率的目录下放置了各种文件,然后现在主流的机型都是 xxh 分辨率,游戏游戏中心针对了内置的 APK,配置了优先使用"xxhdpi", "night-xxhdpi"。

这么配置如果 xxhdpi、night-xxhdpi 存在资源文件,就会优先使用该分辨率目录下文件,如果不存在则会取原来分辨率目录下子资源,能避免出现资源找不到的情形。

defaultConfig {
resConfigs isNotBaselineApk ? "" : ["xxhdpi", "night-xxhdpi"]
}

4.5 内置包去除 v1 签名

同样对于内置包来说,肯定都是 Android 7 及以上的机型了,可以考虑去掉 v1 签名。

signingConfigs {
gameConfig {
if (isNotBaselineApk) {
print("v1SigningEnabled true")
v1SigningEnabled true
} else {
print("v1SigningEnabled false")
v1SigningEnabled false
}
v2SigningEnabled true
}
}

去掉 v1 签名后,上图的三个文件在 APK 中会消失,也能较少 600k 左右的体积。

4.6 动效资源文件优化

发现项目中用了不少的 GIF、Lottie 文件、SVG 文件,占用了很大一部分体积。考虑将这部分替换成更小的动画文件,目前游戏中心接入了 PAG 方案。替换了部分 GIF 图和 Lottie 文件。

PAG 文件采用可扩展的二进制文件格式,可单文件集成图片音频等资源,导出相同的 AE 动效内容,在文件解码速度和压缩率上均大幅领先于同类型方案,大约为 Lottie 的 0.5 倍,SVG 的 0.2 倍。

实际上可能由于设计导出的 Lottie 或者 GIF 不规范,在导出 PAG 文件时会提醒优化点,实际部分资源的压缩比率达到了 80~90%,部分动效资源从几百 K 降到了几十 K。

具体可以参考 PAG 官网:https://github.com/Tencent/libpag/blob/main/README.zh_CN.md

游戏中心这边将比较大的 GIF 图,较多的 Lottie 图做过 PAG 替换。

举例

(1)游戏中心的榜单排行页上的头图,UI 那边导出的符合效果的 GIF 大小为 701K,替换为 PAG 格式后同样效果的图大小为 67K,只有原来的 1/10 不到。

(2)游戏中心的入口空间 Lottie 动效优化。

一份 Lottie 动效大概是这样的,一堆资源问题加上 Json 文件。像上述动效的整体资源为 112K,同样的动效格式转换为 PAG 格式后,资源大小变成 6K,只有原大小的 5%左右。之后新的动效会优先考虑使用 PAG。

4.7 编译期间优化图片

以游戏中心 App 为例,图片资源约占用了 25%的包体积,因此对图片压缩是能立杆见效的方式。

WebP 格式相比传统的 PNG 、JPG 等图片压缩率更高,并且同时支持有损、无损、和透明度。

思路就是在是在 mergeRes 和 processRes 任务之间插入 WebP 压缩任务,利用 Cwebp 对图片在编译期间压缩。

(注:图片来源于https://booster.johnsonlee.io/zh/guide/shrinking/png-compression.html#pngquant-provider

已有的解决方法

(1)可以采用滴滴的方案 booster,booster-task-compression-cwebp 。

参考链接:https://github.com/didi/booster

(2)公司内部官网模块也有类似基于 booster 的插件,基于 booster 提供的 API 实现的图片压缩插件。压缩过后需要对所有页面进行一次点检,防止图片失真,针对失真的图片,可以采用白名单的机制。

4.8 动态化加载 so

同样以游戏中心为例,so 的占比达到了 45.1%,可以对使用场景较少和较大的 so 进行动态化加载的策略,在需要使用的场景下载到本地,动态去加载。

使用的场景去服务端下载到本地加载的流程可以由以下流程图表示。

流程可以归纳为下载、解压、加载,主要问题就是解决 so 加载问题。

载入 so 库的传统做法是使用:

System.loadLibrary(library);

经常会出现 UnsatisfiedLinkError,Relinker 库能大幅减小报错的概率:

ReLinker.loadLibrary(context, "mylibrary")

具体可以参考:https://github.com/KeepSafe/ReLinker

按需加载的情形,风险与收益是并存的,有很多情况需要考虑到,比如下载触发场景、网络环境、加载失败是否有降级策略等等,也需要做好给用户的提示交互。

4.9 内置包只放 64 位 so

目前新上市的手机 CPU 架构都是 arm64-v8a, 对应着 ARMV8 架构,所以在打包的时候针对内置项目,只打包 64 位 so 进去。

ndk {
if ("64" == localMultilib)
abiFilters "arm64-v8a"
else if ("32" == localMultilib)
abiFilters "armeabi"
else
abiFilters "armeabi", "arm64-v8a"
}
//其中localMultilib为配置项变量 String localMultilib = getLocalMultilib()
String getLocalMultilib() {
def propertyKey = "LOCAL_MULTILIB"
def propertyValue = rootProject.hasProperty(propertyKey) ? rootProject.getProperty(propertyKey) : "both"
println " --> ${project.name}: $propertyKey[$propertyValue], $propertyKey[${propertyValue.class}]"
return propertyValue
}

4.10 开启代码混淆、移除无用资源、ProGuard 混淆代码

android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
}

shrinkResources 和 minifyEnabled 必须同时开启才有效。

特别注意:这里需要强调一点的是开启之后无用的资源或者图片并没有真正的移除掉,而是用了一个同名的占位符号。

可以通过 ProGuard 来实现的,ProGuard 会检测和移除代码中未使用的类、字段、方法和属性,除此外还可以优化字节码,移除未使用的代码指令,以及用短名称混淆类、字段和方法。

proguard-android.txt 是 Android 提供的默认混淆配置文件,在配置的 Android sdk /tools/proguard 目录下,proguard-rules.pro 是我们自定义的混淆配置文件,我们可以将我们自定义的混淆规则放在里面。

android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
}
}

4.11 R 文件内联优化

如果我们的 App 架构如下:

编译打包时每个模块生成的 R 文件如下:

R_lib1 = R_lib1;
R_lib2 = R_lib2;
R_lib3 = R_lib3;
R_biz1 = R_lib1 + R_lib2 + R_lib3 + R_biz1(biz1本身的R)
R_biz2 = R_lib2 + R_lib3 + R_biz2(biz2本身的R)
R_app = R_lib1 + R_lib2 + R_lib3 + R_biz1 + R_biz2 + R_app(app本身R)

可以看出各个模块的 R 文件都会包含下层组件的 R 文件内容,下层的模块生成的 id 除了自己会生成一个 R 文件外,同时也会在全局的 R 文件生成一个,R 文件的数量同样会膨胀上升。多模块情况下,会导致 APK 中的 R 文件将急剧的膨胀,对包体积的影响很大。

由于 App 模块目前的 R 文件中的资源 ID 全部是 final 的, Java 编译器在编译时会将 final 常量进行 inline 内联操作,将变量替换为常量值,这样项目中就不存在对于 App 模块 R 文件的引用了,这样在代码缩减阶段,App 模块 R 文件就会被移除,从而达到包体积优化的目的。

基于以上原理,如果我们将 library 模块中的资源 ID 也转化为常量的话,那么 library 模块的 R 文件也可以移除了,这样就可以有效地减少我们的包体积。

现在有不少开源的 R 文件内联方法,比如滴滴开源的 booster 与字节开源的 bytex 都包含了 R 文件内联的插件。

booster 参考:

https://booster.johnsonlee.io/zh/guide/shrinking/res-index-inlining.html#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8

bytex 参考:

https://github.com/bytedance/ByteX/blob/master/access-inline-plugin/README-zh.md

五、优化效果

5.1 优化效果

上述优化措施均在游戏中心实际中采用,以游戏中心某个相同的版本为例子,前后体积对比如下图所示:

(1)包体积优化的比例达到了 31%,包体积下降了 20M 左右,从长久来说对应用的转换率可以提升 3%的点左右。

(2)启动速度相对于未优化版本提升 2.2%个点。

5.2 总结

(1)读者想进行体积优化之前,需先分析下 APK 的各个模块占比,主要针对占比高的部分进行优化,比如:游戏中心中 lib、res、assets、resources 占比较高,就针对性的进行了优化;

(2)动效方案的切换、so 动态加载、编译期间图片优化等措施是长久的,相比于未进行优化,时间越长可能减少的体积越明显;

(3)资源文件最小化配置、配置资源优化,简单且效果显著;

(4)后续会对 dex 进行进一步探索,目前项目中代码基本上都在做加法,越来越复杂,很少有做减法,导致 dex 逐渐增大,目前还在探索怎么进一步缩小 dex 体积。

vivo 游戏中心包体积优化方案与实践的更多相关文章

  1. 从C#垃圾回收(GC)机制中挖掘性能优化方案

    GC,Garbage Collect,中文意思就是垃圾回收,指的是系统中的内存的分配和回收管理.其对系统性能的影响是不可小觑的.今天就来说一下关于GC优化的东西,这里并不着重说概念和理论,主要说一些实 ...

  2. Adapter优化方案的探索

    概要:使用Adapter的注意事项与优化方案本文的例子都可以在结尾处的示例代码连接中看到并下载,如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以提交pull request. ...

  3. Unity优化方向——优化Unity游戏中的图形渲染(译)

    CPU bound:CPU性能边界,是指CPU计算时一直处于占用率很高的情况. GPU bound:GPU性能边界,同样的是指GPU计算时一直处于占用率很高的情况. 原文:https://unity3 ...

  4. Unity优化方向——优化Unity游戏中的脚本(译)

    原文地址:https://unity3d.com/cn/learn/tutorials/topics/performance-optimization/optimizing-scripts-unity ...

  5. web性能优化之---JavaScript中的无阻塞加载性能优化方案

    一.js阻塞特性 JS 有个很无语的阻塞特性,就是当浏览器在执行JS 代码时,不能同时做其他任何事情,无论其代码是内嵌的还是外部的. 即<script>每次出现都会让页面等待脚本的解析和执 ...

  6. Unity优化方向——优化Unity游戏中的垃圾回收(译)

    介绍 当我们的游戏运行时,它使用内存来存储数据.当不再需要该数据时,存储该数据的内存将被释放,以便可以重用.垃圾是用来存储数据但不再使用的内存的术语.垃圾回收是该内存再次可用以进行重用的进程的名称. ...

  7. Unity3D 游戏开发之内存优化

    项目的性能优化主要围绕CPU.GPU和内存三大方面进行. 无论是游戏还是VR应用,内存管理都是其研发阶段的重中之重. 然而,在我们测评过的大量项目中,90%以上的项目都存在不同程度的内存使用问题.就目 ...

  8. [转] 钉钉的H5性能优化方案

    对于一个H5的产品,功能无疑很重要,但是性能同样是用户体验中不可或缺的一环.原本H5的渲染性能就不及native的app,如果不把性能优化做起来,将极大地影响用户使用产品的积极性. 用户感受 当用户能 ...

  9. 从0到1建设智能灰度数据体系:以vivo游戏中心为例

    作者: vivo 互联网数据分析团队-Dong Chenwei vivo 互联网大数据团队-Qin Cancan.Zeng Kun 本文介绍了vivo游戏中心在灰度数据分析体系上的实践经验,从&quo ...

  10. vivo 游戏中心低代码平台的提效秘诀

    作者:vivo 互联网服务器团队- Chen Wenyang 本文根据陈文洋老师在"2022 vivo开发者大会"现场演讲内容整理而成.公众号回复[2022 VDC]获取互联网技术 ...

随机推荐

  1. GNU make-Makefile文件简介

    Makefile简介 Makefile是一种特殊格式的文件,被make工具使用来管理软件的构建过程.Makefile定义了一组规则和依赖关系,告诉make如何编译和链接程序. Makefile语句 T ...

  2. MarginNote 4 内存泄露?

    在床上用电脑的时候突然发现电脑风扇呼呼响,一摸很烫,以为是被子把出风口堵住了,于是调整角度继续用.结果一段时间之后风扇还是狂转不停,然后收到了这样的提示.不看不知道一看吓一跳,MarginNote 4 ...

  3. vue 报错 Component template should contain exactly one root element. If you are using v-if on multiple elements

    vue 报错 Component template should contain exactly one root element. If you are using v-if on multiple ...

  4. web 3d 技术预研及数据可视化技术

    web 3D 技术 白纸一张,先理一理概念: webGL 是low level 库,three.js 是high level 库,一般只要理解webGL里的概念,实际用thee.js 开发更容易 3D ...

  5. 《linux实用指令积累》持续更新。。。

    一.远程服务器文件拷贝 1.1.scp scp /home/a.txt root@127.0.0.1:/home/ 1.2.sshpass(适用于脚本调用,直接指定密码) 1.2.1.安装sshpas ...

  6. tracking调研

    常用框架有以下三种:       Separate Detection and Embedding (SDE- 物体检测,特征提取与物体关联),JOINT Detection and Embeddin ...

  7. Identity – user login, forgot & reset password, 2fa, external login, logout 实战篇

    前言 之前写过一篇 Identity – User Login, Forgot Password, Reset Password, Logout, 当时写的比较简陋, 今天有机会就写多一篇实战版. 建 ...

  8. Python写入数据到MySQL数据库并读取

    1.导入pymysql库   命令行输入pip3 install PyMySQL    2.连接数据库 import pymysql db = pymysql.connect(host='localh ...

  9. 【赵渝强老师】Kubernetes平台中日志收集方案

    一.K8s整体日志收集方案 整体的日志收集方案,如下图所示: Filebeat是本地文件的日志数据采集器,可监控日志目录或特定日志文件(tail file),并将它们转发给Elasticsearch或 ...

  10. Phpstorm 插件

    Symfony Plugin - 支持 Symfony 2,3,4 ...Laravel Plugin - 支持 Laravel.env files support - 支持.env 文件BashSu ...