【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

荔枝集团 Android-Cocos 复合型 App 游戏启动优化分享|图文详解

胡骏麒

  • 2023-03-20
    北京
  • 本文字数:3939 字

    阅读完需:约 13 分钟

荔枝集团Android-Cocos复合型App游戏启动优化分享|图文详解

作者:胡骏麒,荔枝集团业务技术中心高级 Android 工程师,邮箱:hujunqi@lizhi.fm


随着荔枝集团发展越来越快,集团旗下产品也上线了游戏复合型产品,但目前产品线上 Cocos 游戏存在加载速度慢的问题。从线上数据可以看得出,约 37%的数据花了 4000 毫秒以上的时间才能够进入到游戏,约 54%耗时则在 2000 毫秒到 4000 毫秒之间,仅有 8.25%的数据耗时少于 2000 毫秒,并且总体的耗时中位数是 3380.5 毫秒。


总体来说,Android Cocos 原生化游戏的初始化-进入游戏自上线之后就被产运以及技术团队内诟病,是一个极度影响用户体验的关键点,也可能会有用户在等待进入游戏过久导致转化率降低。

问题所在


自 Android 从 Cocos Web 方案接入了 Cocos 原生化方案之后,Cocos 游戏加载速度从正常变成了目前很慢的速度。原因于在代码层面上之前的 Web 的接入方式完全不一样,不仅是加载的方式不一样了,原有的页面结构也需要大改,比如原有的首页 Activity 需要继承 CocosActivity,游戏会因为 CocosActivity 的生命周期回调被暂停或恢复。


这也是跟 Cocos 官方提供的接入方式有关,Cocos 官方的使用场景是整个 App 就是一个游戏,但是与我们的产品相悖,我们的 AndroidApp 是一个复合 App 包含原生,flutter,H5,Cocos 游戏多重技术栈,这就导致游戏存在一定的问题,比如目前绝大部分 AndroidApp 都是以 Activity 作为主要页面组件,当 CocosActivity 进入后台之后,游戏就会被暂停导致无法进行游戏预加载,这就会明显导致进入游戏场景的速度很慢,明显影响到用户体验。

分析

以 Cocos 3.6.1 版本为准,其他版本可能存在差异。

Cocos 是如何被 App 控制的?


Activity 生命周期的简化图示。


Android 是以 Activity 的形式作为页面载体,Activity 不仅仅是一个 UI 层面的组件,它还是一个重要的具有 IPC 跨进程通信功能组件,并且有许多生命周期回调比如初始化 onCreate 等回调,并且这些回调也表明了相对应的状态。

Activity 是如何被 AMS(ActivityManagerService)创建的


Activity 是如何被 AMS(ActivityManagerService)创建的但是 Activity 的创建,各个运行状态都是通过 AMS(ActivityManagerService)管理的,也可以简单的说开发者是不无法通过正常手段自行管理 Activity 的创建,运行以及销毁。那为什么要先介绍 Activity 的基本知识呢?因为 Cocos 在 Android 平台的原生化是完全依赖到了 Activity,应该说是依赖了 Google Android Game Development Kit 里的 GameActivity

Google Android Game Development Kit 又是什么?它有扮演了什么角色?


Android Game Development Kit (AGDK) 包含一套工具和库,可帮助您开发和优化 Android 游戏,同时还能与现有游戏开发平台和工作流程集成。


GameActivity 是一个 Jetpack 库,旨在帮助 Android 游戏在应用的 C/C++ 代码中处理应用周期命令、输入事件和文本输入。GameActivity 是 NativeActivity 的直接后代,具有类似的架构:


如上图所示,GameActivity 执行以下功能:

•通过 Java 端组件同 Android 框架进行交互。

•将应用周期命令、输入事件和输入文本传递到原生端。

•将 C/C++ 源代码建模为三个逻辑组件:

○GameActivity 的 JNI 函数,直接支持 GameActivity 的 Java 功能,并会将事件加入 native_app_glue 中的队列。

○native_app_glue,主要在自己的原生线程(不同于应用的主线程)中运行,并且使用其 Looper 执行任务。

○应用的游戏代码,负责轮询和处理在 native_app_glue 内排队的事件,并在 native_app_glue 线程中执行游戏代码。


借助 GameActivity,您可以专注于核心游戏开发,并避免花费过多时间处理 JNI 代码。那么 Cocos 引擎是如何对接到 AGDK 里的呢?我以暂停为例:


Java 层 GameActivity 将 Stop 生命周期回调通过 JNI 调用到 C++层 GameActivity 的 onNativeStop 到 android_native_app_glue 里的 onPause,android_native_app_glue 通过 Pipe 管道将 APP_CMD_STOP 事件从主线程切换到游戏的主线程,将事件传递给了 AndroidPlatform,并且 AndroidPlatform 则把_isVisible 修改成 false。


每当 AndroidPlatform 的一层循环调用的时候会检查_isVisible && _hasWindow 状态,如果 TRUE 就会继续游戏主线程逻辑,否则跳过。


总结:

1.GameActivity 成为了 Java 层与 C++层标准化桥梁,提供了一套对接方法。

2.Cocos 游戏主线程会受 GameActivity 生命回调暂停或恢复主线程逻辑。

3.大部分 Android App 是以多个 Activity 作为页面栈进行管理,CocosActivity 自然会因为生命周期调用被暂停。

Cocos 是如何获得 Surface?


从上图可以看得到 Cocos 利用 GameActivity 同步 Java 层生命周期调用,并且根据_isVisible && _hasWindow 状态,onStop 控制_isVisible,那_hasWindow 是又是被谁控制呢?


ViewRootImpl 被调用 performTraversals 之后通过 mWindowSession 请求 WMS(WindowManagerService)对 Window 进行 relayout,当 Native 的 Surface 真正被创建之后,ViewRootImpl 调用 notifySurfaceCreated,将回调调用到 Cocos 的 SurfaceView,然后 SurfaceView 才调用到注册了 SurfaceHolder 的 GameActivity。


从上图可以得出:

1.Surface 是由 WMS 管理,Activity 进入前台则会获取,反之 Activity 进入后台则会被释放。

2.Cocos 引擎根据 surface 是否有效暂停或恢复主线程逻辑。

小结

从以上分析,我们可以得出以下结论:

1.GameActivity 成为了 Java 层与 C++层标准化桥梁,提供了一套对接方法。

2.Cocos 游戏主线程会受 GameActivity 生命回调暂停或恢复主线程逻辑。

3.绝大部分 Android App 是以多个 Activity 作为页面栈进行管理,CocosActivity 自然会因为生命周期调用被暂停。

4.Surface 是由 WMS 管理,Activiyt 进入前台则会获取,反之 Activity 进入后台则会被释放。

5.Cocos 引擎根据 surface 是否有效暂停或恢复主线程逻辑。


而导致游戏被暂停从而致使游戏初始化-进入游戏的时间耗时的原因则是:


1.游戏开启需要切换到首页,在切换到首页之前,游戏无法恢复主线程运行

2.Surface 获取需要时间,且需要切换到首页之后才能够被获取,游戏无法恢复主线程运行只要解决以上 2 个点,那就可以在游戏加载上有巨大的提升。

解决方案


目前问题最紧迫的是游戏加载速度过慢的问题,提高用户体验,尽可能需要有一个成本最低的方案且对后续 Cocos 升级不会有产生影响。

与 GameActivty 解耦


1.GameActivity 成为了 Java 层与 C++层标准化桥梁,提供了一套对接方法。

2.Cocos 游戏主线程会受 GameActivity 生命回调暂停或恢复主线程逻辑。


从上面的分析可以得出:GameActivity 其实是是一个标准化的桥梁,是一个控制器,但只是因为 Activity 是被 AMS 管理才无法控制,那有没有可能通过技术手段将 GameActivity 被我控制?Android Activity 是由 AMS 管理,并且又由 ActivityThread 用 Classloader 动态加载 Class,并且在创建的过程中会创建 PhoneWindow 以及 attach 比如 Application 等,但我们可以换个角度去思考这个事情。

我们将 GameActivity 看作为一个控制器,Activity 已经帮我处理好了各种状态,但如果直接将 Activity 拿来用的话是会出问题的,会出现类似这样的崩溃。





还记得上面的说的:

在创建的过程中会创建 PhoneWindow 以及 attach 比如 Application 等


针对以上两个点,我们只需要两点处理,通过以下方式则可规避掉崩溃的问题:

1.利用外部的 Activity 提供的 PhoneWindow

2.通过反射的方式,将 Application 设置到 GameActivity


那面对 C++层对 Java 的调用该怎么处理呢?


因为 C++调用 Java 是通过反射的方式去调用的,只要 class 和 method 的方法名是正确的,我们就可以正确做到桥接。以上,就已完成了 GameActivity 的基本解耦,当然还有别的地方需要改动,但方法论并没有变。

规避 Surface 获取的困难


从上面的流程图其实可以看得出,Surface 的获取是根据上层决定的,当 Activity 进入到后台之后(有一个新的 Activity 覆盖或 App 进入到后台),Surface 就会销毁,所以想要游戏能在不一样的 Activity 上运行,在对 GameActivity 解耦之后,仅仅需要将 SurfaceView 进行 addView 到具体的 Activity 里的 ViewGroup 里即可。


为了提升游戏的加载速度,采取了一套预先加载的策略:

1.在匹配玩家过程中将 SurfaceView 添加到匹配页面

2.在匹配页面等待游戏加载完成,达成只要切换页面游戏即可加载完成的目的


以上方式还解决了一个问题,那就是 Cocos 引擎初始化以及进入默认场景的主线程被原有的 GameActivity 中断的问题。比如用户的行为是不可阻碍的,任何页面切换都导致 GameActivity 进入到了后台,那会暂停 Cocos 引擎并且 Surface 也会被释放。所以利用匹配玩家过程中的耗时去同样消耗 Cocos 引擎初始化以及进入默认场景的耗时,这样就可以避免上述问题的发生。

成果


数据采集方式:

模拟用户行为:App 进入到首页之后就点击档位选择页并且进行匹配游戏,每次进入完游戏之后,杀掉 App 再进行测试。


中低端机代表:三星 A13 5G

高端机代表:一加 10


从数据以及体感上来看的话,Cocos 游戏改造优化后带来的提升十分的明显,也恢复到了正常且较为优秀的加载速度了。

线上数据


1.耗时小于 2000 毫秒从原有的 8.25%大幅升至 48.86%,且从原先的最少区间变为最大区间

2.耗时小于 4000 毫秒从原有 62.28%大幅升至 82.37%,绝大部分数据都在 4000 毫秒以内

3.数据中位数从 3380.5 毫秒减少到 2033.5 毫秒,普遍具有一秒以上的速度提升

结论


通过系统性对整个框架的分析,得出一套仅在 Java 做修改就可以极大提升游戏加载速度的方案,并且与 GameActivity 解耦,承载游戏的 SurfaceView 能够在任意 Activity 正确显示,仅改动了 Android 平台上层代码,Android 端实现了游戏线程自主控制,不仅仅是用户体验的提升,优化了项目结构,还为未来游戏-原生跨环境业务发展提供了底层支持。

引用


Android Game Development Kit:https://developer.android.google.cn/games/agdk/overview?hl=zh-cn

关于荔枝集团


荔枝集团打造了综合性的全球化音频生态系统,致力于通过多样化产品组合满足用户对于音频娱乐以及在线社交的需求,使每个人都能在全球化的音频生态系统中通过声音连接与互动。荔枝公司于 2020 年 1 月在纳斯达克上市。

2023-03-20 17:572842

评论 3 条评论

发布
用户头像
做了一个同样的功能,只不过是将GameActivity完全拆开了,做成了不依赖Activity的。然后在自己的页面去控制生命周期,能达到一样的效果~
2024-01-31 11:01 · 广东
回复
用户头像
大佬你好,承载cocos的activity必须在单独的activity栈里吗
2023-11-03 18:00 · 北京
回复
用户头像
大佬,有没有demo链接
2023-08-11 15:07 · 山西
回复
没有更多了
发现更多内容

Alibaba官方发文:阿里技术人的成长路径与方法论

Java架构师迁哥

架构师训练营 - 第五周课后练习

joshuamai

讯飞推出充电宝式便携拾音器,重新定义传统拾音

Talk A.I.

表格控件Spread.NET V14.0 发布:支持 .NET 5 和 .NET Core 3.1

葡萄城技术团队

大厂经验:一套Web自动曝光埋点技术方案

阿亮

埋点 曝光埋点 点击埋点 自动化埋点

SpringBoot-技术专题-如何提高吞吐量

洛神灬殇

计算机核心课程必读书目——《高级数据结构:理论与应用》

计算机与AI

数据结构 算法

Architecture Phase1 Week10:Summarize

phylony-lu

极客大学架构师训练营

甲方日常 57

句子

工作 随笔杂谈 日常

Java踩坑记系列之BigDecimal

Java老k

BigDecimal

京东千亿订单背后的纵深安全防御体系

京东科技开发者

安全 网络 云服务 云安全

架构师训练营 W06 作业

Geek_f06ede

架构师训练营 - 第五周学习总结

joshuamai

Java踩坑记系列之Arrays.AsList

Java老k

Java

java: Compilation failed: internal java compiler error解决办法

LSJ

IDEA

推荐几款MySQL相关工具

Simon

MySQL 工具 percona server

成德眉资现代农业园区大联动促发展,“1链3e”引领四市农业产业数字化建设

CNG农业公链

MyBatis-技术专题-拦截器原理探究

洛神灬殇

“奋斗者”号下潜10909米:我们为什么要做深海探索?

脑极体

面试者必看:Java8中的默认方法

Silently9527

java8 默认方法

肝了一周的 UDP 基础知识终于出来了。

cxuan

计算机网络 计算机基础

《华为数据之道》读书笔记:第 4 章 面向“业务交易”的信息架构建设

方志

数据中台 数字化转型 数据治理

关于 AWS Lambda 中的冷启动,你想了解的信息都在这!

donghui

Serverless Faas 函数计算

熬夜不睡觉整理ELK技术文档,从此摆脱靠百度的工作(附源码)

996小迁

Java 编程 架构 面试 ELK

Thread.start() ,它是怎么让线程启动的呢?

小傅哥

Java 线程 JVM 小傅哥 Thread

「干货总结」程序员必知必会的十大排序算法

bigsai

排序 排序算法 快速排序

前端高效开发必备的 js 库梳理

徐小夕

Java GitHub 大前端 js

跨语言跨平台聚合OpenAPI文档从来没有这么简单过

Knife4j

微服务 OpenAPI Knife4j Knife4jAggregation

开源认证和访问控制的利器keycloak使用简介

程序那些事

开源 程序那些事 授权框架 keycloak 认证授权

802.11抓包软件对比之Microsoft Network Monitor

IoT云工坊

wifi 嵌入式 抓包

以 Kubernetes 为代表的容器技术,已成为云计算的新界面

阿里巴巴云原生

云计算 Kubernetes 容器 云原生

荔枝集团Android-Cocos复合型App游戏启动优化分享|图文详解_语言 & 开发_InfoQ精选文章