写点什么

如何用游戏引擎改造 Hybrid App?

  • 2018-05-01
  • 本文字数:4105 字

    阅读完需:约 13 分钟

在移动设备上,Hybrid 混合开发的性能问题一直为人诟病,对它的各种优化、hack 层出不穷,但你听说过用游戏引擎来优化 Hybrid 性能的吗?这次我们的 GMTC 邀请到了腾讯高级工程师潘伟洲分享《基于 Cocos 的高性能跨平台应用开发方案》,我们提前对他进行了采访,了解了一些技术细节,分享给大家。

InfoQ:Cocos 作为开源的跨平台游戏引擎框架,是一个什么样的契机让你和你的团队想到了用 Cocos 来改造原本 Hybrid 形式的产品的?

潘伟洲:先从动力说起:ABCmouse 原来的版本出于跨平台的目的,项目的大部分采用的是 Hybrid 的形式,大部分的页面加载都非常耗时,以一个画图的功能为例,从打开到加载完成可能要耗费十几秒的时间,这对于大部分用户来说是难以忍受的。

通过初期技术预研后,我们决定使用 Cocos 来改造这个项目,主要出于以下几个考虑:

  1. 跨平台。Cocos 支持使用同一套代码构建生成 Web、iOS、Android 等几个端,最新的版本还支持发布到微信小游戏、Facebook Instant Games 和 QQ 玩一玩;
  2. 性能。Cocos 的原理是在 Activity 中绘制一个 OpenGL 的 SurfaceView ,并由其完成页面的渲染的。与基于 WebView 渲染的 Hybrid 应用相比,Cocos 的渲染速度更快,性能更好。
  3. 效率。借助可视化的 Cocos Creator 工具,界面的开发和资源的管理非常便捷,设计团队也可以参与进来设计界面和动效,提升开发效率。
  4. 表现力。ABCmouse 中包含了很多诸如游戏、画图、音乐等带游戏和娱乐性质的场景,而 Cocos 本身是为游戏开发设计的,更适合用在我们的产品中。

InfoQ:在实际开发过程当中,与其他跨平台的开发框架相比较,你觉得用 Cocos 的优势在哪里?

潘伟洲:首先是生成的平台更多,除了常见的 Web、iOS、Android 外,难得的还支持微信小游戏、Facebook Instant Games 和 QQ 玩一玩的发布。其次,因为 Cocos 的核心开发团队来自中国,能提供更好的技术支持。腾讯也是 Cocos 的合作伙伴,我们与他们建立了良好的合作关系。

InfoQ:使用 Cocos 开发,你们在技术选型,基础组件储备上做了哪些尝试与创新呢?

潘伟洲:技术选型方面,我们先是为 UI 层设计开发了一些 Cocos 所缺失的一些通用组件,包括:对话框、日期选择器、Toast、Loading 组件等,这些组件能在全部目标平台上通用,并且遵循着统一的接口风格,使得 UI 层的开发更加便利;

在图片加载方面,我们实现了一个带缓存功能的图片加载组件,优化二次加载在线图片的性能,并自动管理内存回收。另外, 为了提高图片的加载性能,我们改进了 Cocos 的图片的加载方式,让其支持使用 ETC2 纹理渲染,使得内存消耗降低了接近 70% 。

由于应用中有非常多的音乐、音效、语音,为了减小包大小,大部分的语音素材放在 CDN 上,需要的时候才从 CDN 上拉取播放。少部分常见的音效会直接打进应用包中。而 Cocos 自带的 AudioEngine 组件在 Native 端只支持本地资源的播放。因此,我们又封装了一个跨平台的音频播放器,可以自动根据指定的音频路径决定使用播放方式:

  • 对于 Web 端或者 Native 端的本地资源文件,直接使用 AudioEngine 来播放。
  • 对于 Native 端的远程音频,使用 Native 的播放器来播放。

由于对外的接口只有一套,开发者无需考虑具体的平台和底层播放器的选择。并且可以使用同样的接口来统一管理不同的音频。

由于客户端大部分的功能都是使用 JS 写的,为了更快速地定位问题,我们在底层对 JS Exception 做了拦截,一旦出现 JS Exception,就将错误信息通过 Toast 展示出来,并且详细的给出了堆栈信息和场景信息。这个错误展示帮助我们发现并修复了很多 JS 错误。

除了以上所提到的几个能力,我们在 Cocos 层还提供了通用的日志记录、事件上报等。这些在不同的端有不同的实现,但都通过 Cocos 层进行了适配,使得对开发者都是统一的一套接口。

InfoQ:在开发设计 ScrollView 控件时,你们做了哪些优化?

潘伟洲:官方 ScrollView 组件需要配合 layout 组件,当一次加载大量的子节点组件,或者分帧加载单个子节点组件时,初始化 ScrollView 节点视图会比较慢,在加载完成前存在拖动掉帧的问题。另外,一次性加载所有节点,也会导致内存资源的浪费。

我们对 ScrollView 进行了重写,基本的优化思路是:一次仅加载页面可容纳的少量数目子节点。并在滚动过程中,回收不可视的子节点组件并重用。

具体来说,ScrollView 大多数情况下表现为列表组件和宫格组件,以列表组件为例,可以根据子节点数目和子节点大小,计算出整个 ScrollView 内容的宽高,同时计算出屏幕可视区域最多可以容纳的子节点行数 rows,加载时仅加载 rows + 2 个子节点组件,其中添加的 2 个字节点组件作为滚动回收缓冲。举个具体点的例子,当手势向上,内容往下滚动时,一旦最上面一个子节点组件 A 不可视,就立马回收掉 A 并将其重用于将要渲染的子节点组件。

我们做过测试,我们有个页面加载了非常多的内容,在优化前,拖动的时候帧率可能会跌到 8 fps,而使用优化后的 ScrollView,帧率能够稳定在 60 fps。

InfoQ:在实际运用 Cocos 开发过程中,你们有踩过哪些坑呢?包大小的问题又是怎么解决的呢?

潘伟洲:我们遇到的第一个坑和 Cocos 的 VideoPlayer 组件有关:Cocos 的基本原理是在 Activity 里头展示一个 OpenGL 的 SurfaceView ,这个 SurfaceView 不能直接绘制视频。

为了解决视频的播放问题,Cocos 的引擎开发者们将视频播放器作为独立的一层 View ,并保持置顶。这带来的问题是:VideoPlayer 组件上无法绘制任何其他组件。比如,如果希望定制播放按钮、进度条等元素,使用 Cocos 的 VideoPlayer 是做不到的。

最终我们放弃了在 Cocos 层开发视频播放器的功能,而是在底层为各个端开发视频播放器,并各自实现界面的定制。

第二个坑是跨层调用的响应问题。Native 层和 Cocos 层交互时,存在一定的响应时间。

比如,前面说到,我们的视频播放器是在 Native 层开发的,我们有一个需求:当视频播放完后,立即跳转到另一个 Cocos 的场景。然而,由于跨层调用存在一定响应时间,当把视频播放器的 activity 关闭时,此时页面会回到调起视频播放器前的场景,然后才进入一个新的场景。

为了解决这个问题,我们在 Cocos 层设计了一个隐藏的常驻节点,在关闭 activity 前,先把这个节点设为可见,并将其颜色改为下一个场景的背景色,直到下一个场景加载完毕时才重新将该节点设为隐藏。由于设置节点的可见性远比加载场景要快,此时再关闭 activity 时,用户先看到的是这个节点,给用户一种即将进入下一个场景的错觉,就不会觉得场景的跳转很突兀。

最后我们遇到的一个比较严重的问题是 local reference table overflow error 问题。

为了复用 Native 端的能力,我们在 Cocos 层大量地使用反射机制来调用 Native 端提供的方法。然而,我们经常会遇到 local reference table overflow error 错误导致的界面卡死问题。最初,我们怀疑是反射调用使用得太频繁导致。因此,我们对诸如打 log、事件上报等 Native 方法进行了频率限制,例如使用缓冲的方法将多个 log 合并后再打印。

然而,虽然这个做法减少了界面卡死的发生,但依然没有彻底杜绝问题的再次出现,就像是一个定时炸弹一样,威胁着我们应用的稳定性。通过阅读引擎的代码,我们发现 Cocos 的引擎在反射阶段处理字符串参数时,使用了 NewStringUTF() 方法将其转换为 JNI 层的字符串,然而在调用执行完成后并没有相应地使用 DeleteLocalRef() 释放该字符串的引用,从而导致了引用表的溢出。了解到这个原因后,我们给 Cocos 的引擎提交了一个 pull request,修复了这个问题。

在包大小的控制方面,我们都知道图片资源往往是导致应用包臃肿的元凶。因此,为了减少包大小,我们应该能够找出工程里头的无用资源,以及可优化的资源。

针对无用资源,Cocos 建议将静态引用的图片存在 res 目录,而将动态引用的图片放在 resource 目录。这么区分的好处是,res 目录下的图片只有存在引用时才会被打包进应用,而没有被引用的图片则不会被打包进应用。

因此,我们首先编写了静态分析脚本,找出工程中不合理放置的图片资源(例如将静态引用的图片放置到 resource 目录的情况),并移除 resource 中没有使用的图片。另外,对于 res 目录,我们也会经常检查是否存在不必要的引用,比如移除废弃的场景或者节点等。

可以优化的资源指的是可以进一步压缩的资源,这个和原生应用的优化思路差不多。比如合理的使用有损压缩 / 无损压缩工具来压缩图片文件。对于不需要透明度的图片资源,则使用 JPEG 格式代替 PNG 格式。

另外,在实现一些动效时,我们也尽量使用属性动画而不是帧动画,以节省空间。在这一点上,Cocos 的优势比较明显。我们的动效通常是设计团队直接使用 Cocos Creator 的动画编辑器创建的动画剪辑,并非简单的帧动画。

InfoQ:在什么样的场景下,才适合做基于 Cocos 的产品开发呢?

潘伟洲:取决于产品的定位。Cocos 主要是为游戏开发设计的,所以产品的表现力更佳,但在性能、内存占用、耗电上不如普通原生应用。当你的产品要求有更高的表现力,UI 开发更灵活,并且希望能实现跨平台开发时,Cocos 是一个值得考虑的选择。

InfoQ:在众多的跨平台 app 开发框架当中,开发者如何找到适合自己的框架?

潘伟洲:建议结合产品的需求、定位、框架成熟度、社区支持情况等维度进行综合考量。

比如,如果你要开发一个视频播放器,那么像 Cocos 这种使用 OpenGL 绘制 UI 的框架就难以实现跨平台开发。而如果要开发一个高表现力的应用,比如内置小游戏或者是带翻书特效的阅读应用,那么就应该考虑 Cocos 这类支持 OpenGL 绘制的框架,或者与 H5 页面结合的 Hybrid 框架。

另外,如果产品对性能要求更高,那么应该选择更 “Native” 的框架,例如 React Native、Weex ,或者 Flutter 。而如果产品比较轻量,也更看重开发效率,那么可以选择 Hybrid 的框架。

除了从产品自身的需求定位出发,选择一个框架还应该考虑这个框架本身的成熟度以及社区支持情况。比如,Google 的 Flutter 是最近比较受关注的跨平台开发框架,它的亮点在于跨平台、高性能和不俗的表现力。然而这个框架目前还不够成熟,社区的轮子还不够丰富,能够得到的帮助也比较有限,所以建议现阶段可以保持关注,但小团队不要贸然去尝试。

感谢覃云对本文的审校。

2018-05-01 18:162848

评论

发布
暂无评论
发现更多内容

OpenHarmony社区运营报告(2023年3月)

OpenHarmony开发者

OpenHarmony

更安全、更低耗的微服务架构改造之道

华为云开发者联盟

云计算 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

【坚果派-坚果】OpenHarmony新增并编译芯片解决方案

坚果

OpenHarmony OpenHarmony3.2 三周年连更

阿里P8架构师爆肝分享内部开源的JVM垃圾回收PDF文档,共23.3W字

做梦都在改BUG

Java JVM 垃圾回收

每日 Scrum 与站立会议:有什么区别?

码语者

Scrum

JavaScript 对象遍历为什么要使用 hasOwnProperty 检查属性

Lee Chen

JavaScript

macbook触摸板怎么按右键

互联网搬砖工作者

Java中的异常处理详解(try、catch、finally、throw、throws) | 社区征文

共饮一杯无

Java 异常处理 三周年连更

关键的Java JVM选项和参数

码语者

JVM

国内功率半导体需求将持续快速增长

华秋电子

各大金融企业都在用的堡垒机-行云管家堡垒机

行云管家

金融 数据安全 堡垒机

前端开发:未来依旧光明 | 社区征文

海拥(haiyong.site)

三周年征文

牛客网热度最高的17套一线大厂Java面试八股文!面面俱到,太全了

架构师之道

Java 面试

HummerRisk V1.0.0:架构全面升级,开启新篇章

HummerCloud

云原生安全

一名开发者眼中的TiDB与MySQL选择

TiDB 社区干货传送门

数据库架构选型

精华!Redis 知识总结

会踢球的程序源

Java Java进阶 redis 底层原理

网站不收录是受哪些因素影响?

海拥(haiyong.site)

三周年连更

Parallels Desktop PD 18虚拟机关闭、停止、中止和暂停操作的区别

互联网搬砖工作者

企业级安全运维审计产品-行云管家堡垒机全新V7.0举行线上发布会

行云管家

运维 云堡垒机 安全运维 等级

活久见,java8 lamdba Collectors.toMap()报NPE

做梦都在改BUG

【4.7-4.14】写作社区优秀技术博文一览

InfoQ写作社区官方

热门活动 优质创作周报

总结一下Redis的缓存雪崩、缓存击穿、缓存穿透

做梦都在改BUG

Netty框架详解:高性能网络编程的设计与实现

做梦都在改BUG

网络编程 Netty 高性能

全新适配鸿蒙生态,Cocos引擎助力3D应用开发

HarmonyOS开发者

HarmonyOS

TiDB 6.1/6.5 在 Rocky Linux 8 中的部署升级与 PITR 初体验

TiDB 社区干货传送门

版本升级 安装 & 部署 备份 & 恢复 扩/缩容 6.x 实践

SLBR通过自校准的定位和背景细化来去除可见的水印

合合技术团队

人工智能 图像处理 水印消除

Spring Security 的介绍和简单使用

会踢球的程序源

Java 后端 spring security Java进阶

全面数字化时代,国有大型银行如何走好金融创新之路?

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

华秋PCB生产工艺 | 第十二道主流程之FQC

华秋电子

新手测试必学的 API 接口文档知识

Apifox

测试 入门 接口文档 API API 文档

Greptime 的 GitOps 实践

Greptime 格睿科技

k8s gitops IaC

如何用游戏引擎改造Hybrid App?_语言 & 开发_胡骁杰_InfoQ精选文章