vivo 游戏中心包体积优化方案与实践

作者:来自 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"elseabiFilters "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 trueshrinkResources true}}
}

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

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

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

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

android {buildTypes {release {minifyEnabled trueproguardFiles 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 体积。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/60582.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode 每日一题 125.验证回文串

问题定义 给定一个字符串s,我们需要判断它是否是一个回文串。但在此之前,我们需要将所有大写字符转换为小写字符,并移除所有非字母数字字符。只有经过这样处理后的字符串,我们才进行回文检测。 示例解析 以下是几个示例&#x…

Struts扫盲

Struts扫盲 这里的struts是struts1。以本文记录我的那些复习JavaEE的痛苦并快乐的晚上 Struts是什么 框架的概念想必大家都清楚,框架即“半成品代码”,是为了简化开发而设计的。一个项目有许多分层,拿一个MVC架构的Web应用来说,有…

【AiPPT-注册/登录安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被机器执行自动化程序攻击,存在如下风险: 暴力破解密码,造成用户信息泄露,不符合国家等级保护的要求。短信盗刷带来的拒绝服务风险 ,造成用户无法登陆、注册,大量收到垃圾短信的…

自动驾驶系列—从数据采集到存储:解密自动驾驶传感器数据采集盒子的关键技术

🌟🌟 欢迎来到我的技术小筑,一个专为技术探索者打造的交流空间。在这里,我们不仅分享代码的智慧,还探讨技术的深度与广度。无论您是资深开发者还是技术新手,这里都有一片属于您的天空。让我们在知识的海洋中…

【月之暗面kimi-注册/登录安全分析报告】

前言 由于网站注册入口容易被机器执行自动化程序攻击,存在如下风险: 暴力破解密码,造成用户信息泄露,不符合国家等级保护的要求。短信盗刷带来的拒绝服务风险 ,造成用户无法登陆、注册,大量收到垃圾短信的…

时序预测 | 改进图卷积+informer时间序列预测,pytorch架构

时序预测 | 改进图卷积informer时间序列预测,pytorch架构 目录 时序预测 | 改进图卷积informer时间序列预测,pytorch架构预测效果基本介绍参考资料 预测效果 基本介绍 改进图卷积informer时间序列预测代码 CTR-GC卷积,informer,CTR-GC 图卷积…

从入门到精通:一文掌握 Dockerfile 的用法!(多阶段构建与缓存优化)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 Dockerfile基础用法 📒📝 什么是 Dockerfile?📝 Dockerfile 的常见指令🔖 构建指令🔖 命令指令🎈 完整示例:构建一个 Python Flask 应用🔖 1. 项目结构🔖 2. 编写 Dockerfile🔖 3. 构建和运行 Docker 镜像�…

Android11 修改系统语言

1.定义一个view <RelativeLayoutandroid:id"id/rlChooseLanguage"style"style/SettingAboutItem"><TextViewstyle"style/SettingAboutItemTextView"android:text"string/choose_language" /><ImageView style"st…

【C语言】数据库分库分表

你如何理解数据库分库分表&#xff1f;请谈谈在实际项目中如何实现分库分表。 数据库分库分表是一种数据库优化策略&#xff0c;用于处理大规模数据存储和高并发访问带来的性能挑战。它的核心思想是将单个大数据库拆分成多个小的、独立的数据库&#xff08;分库&#xff09;&am…

Go语言开发基于SQLite数据库实现用户表修改接口(四)

背景 上一章 Go语言开发基于SQLite数据库实现用户表查询详情接口(三) 这一章我们实现用户表的修改接口 代码实现 mapper层 type UserMapper interface {UpdateById(user *model.User, id uint64) error}type userMapper struct { }func (m *userMapper) UpdateById(user *m…

【C++学习(35)】在Linux中基于ucontext实现C++实现协程(Coroutine),基于C++20的co_await 协程的关键字实现协程

文章目录 为什么使用协程协程的理解协程优势协程的原语操作yield 与 resume 是一个switch操作&#xff08;三种实现方式&#xff09;&#xff1a; 基于 ucontext 的协程基于 XFiber 库的操作1 包装上下文2 XFiber 上下文调度器2.1 CreateFiber2.2 Dispatch 基于C20的co_return …

844.比较含退格的字符串

java用 O&#xff08;1&#xff09;空间这个方法&#xff0c;容易挺多bug的… O&#xff08;1&#xff09;空间 #&#xff1a;删除前一个字符 》 从后面开始判断&#xff08;这样可以用跳过的思想&#xff09;不能使用两次 i- - 来处理 # 的操作&#xff0c;会造成误删了前面…

大数据实训室建设的必要性

一、大数据发展的背景 大数据作为当今信息技术领域的核心驱动力&#xff0c;正在深刻地改变着社会的各个方面。它不仅仅是指数据量庞大&#xff0c;更重要的是指数据的多样性、实时性和复杂性。随着云计算、物联网等技术的迅猛发展&#xff0c;大数据已成为推动经济社会发展的…

数据结构------队列(Java语言描述)

一、队列的概念 队列是一种数据结构&#xff0c;它遵循先进先出的原则。就像排队买东西一样&#xff0c;先到的人先得到服务&#xff0c;先进入队列的数据元素先被取出。例如&#xff0c;在一个银行排队系统中&#xff0c;顾客按照到达的先后顺序排队等待办理业务。第一个进入…

SD外向交货单

1、写在前面 1.1、文档说明 本文档将介绍外向交货单的创建、修改、删除、过账以及冲销&#xff0c;并且包含如何使用代码来实现上述过程。另外&#xff0c;还将介绍衍生出的交货单屏幕增强和BAPI维护扩展字段等内容。 1.2、关于外向交货单 在 SAP系统中&#xff0c;外向交货…

请解释Python中的装饰器是什么?如何使用它们?

1、请解释Python中的装饰器是什么&#xff1f;如何使用它们&#xff1f; 在Python中&#xff0c;装饰器是一个接受一个函数作为参数的可调用对象&#xff08;通常是一个函数&#xff09;。装饰器本质上是一个高阶函数&#xff0c;因为它接受一个函数作为参数并返回一个新的函数…

MyBatis——增删查改(XML 方式)

1. 查询 1.1. 简单查询 使用注解的方式主要是完成一些简单的增删查改功能&#xff0c;如果要实现复杂的 SQL 功能&#xff0c;还是建议使用 XML 来配置映射语句&#xff0c;将 SQL 语句写在 XML 配置文件中 如果要操作数据库&#xff0c;需要做以下的配置&#xff0c;与注解…

K8S如何基于Istio实现全链路HTTPS

K8S如何基于Istio实现全链路HTTPS Istio 简介Istio 是什么?为什么选择 Istio?Istio 的核心概念Service Mesh(服务网格)Data Plane(数据平面)Sidecar Mode(边车模式)Ambient Mode(环境模式)Control Plane(控制平面)Istio 的架构与组件Envoy ProxyIstiod其他组件Istio 的流量管…

vue3 中那些常用 靠copy 的内置函数

文章目录 vue3 常用函数罗列总结vue3 中批量注册组件vue3 自定义指令应用define 应用补充defineComponent 不同场景应用实例 vue3 常用函数罗列总结 toRefs 用途&#xff1a; 用于将一个响应式对象&#xff08;例如reactive创建的对象&#xff09;转换为普通对象&#xff0c;其…

docker镜像源,亲测可用,时间2024-11-14

最近由于特殊原因,docker拉取镜像总是失败 如果遇到docker拉取镜像失败 可以通过添加镜像源的方式解决 vim /etc/docker/daemob.json 添加如下内容 {"registry-mirrors": ["https://docker.rainbond.cc"] } 重新加载配置 sudo systemctl daemon-reload…