Cocos2.x版本 HandleScope::Initialize(v8::Isolate*) 问题分析
问题背景
公司使用 Cocos 2.4.15 开发游戏时,发现一个 crash 在 Android 15 以上的设备上非常频繁。起初以为是 16KB 的兼容性问题,但分析后发现包其实是兼容 16KB 的。
在 Cocos 社区中,也有相关讨论:v8::HandleScope::Initialize(v8::Isolate*)。官方曾提供过基于 Activity 在 onDestroy 时的修复方案,但我们发现还有其它触发场景。
Crash 日志示例:
1 | (v8::HandleScope::Initialize(v8::Isolate*)+144) (BuildId: a222c0a549189d49b84a5433428e92f58cba47f0) |
简单分析 crash 的代码片段如下:
1 | void ScriptEngine::cleanup() |
问题就出在这一行:
1 | AutoHandleScope hs; |
查看 AutoHandleScope 构造函数可以发现,它会创建一个 _handleScope 并传入 v8::Isolate::GetCurrent():
1 | public: |
如果 v8::Isolate::GetCurrent() 返回 nullptr,就会直接导致 crash。
问题原因
经过分析发现,当 GLSurfaceView 被回收后,Render 线程也会随之销毁。下次再创建新的 GLThread 时,由于 V8 的 Isolate 是线程独立的,第二次进入 ScriptEngine::init() -> cleanup() -> AutoHandleScope hs 时,v8::Isolate::GetCurrent() 就会返回 nullptr,从而触发 crash。
修复方案
尝试过的几种方法:
- **绕过
AutoHandleScope hs**:不可行,会破坏 V8 资源管理。 - 传入之前的 Isolate:也不可行,因为 Isolate 与线程绑定,线程已销毁。
最终,官方提供的方案是:当 Activity 的 onDestroy 被调用时,直接结束进程(调用 Cocos2dxHelper.terminateProcess())。考虑到该 bug 主要在 Android 15 和 16 上,我们加了版本判断:
1 | if (Build.VERSION.SDK_INT >= 35) Cocos2dxHelper.terminateProcess(); |
通过直接杀掉进程,可以彻底避免 crash。
其他触发场景
Activity onDestroy 的问题解决后,仍然存在另一个触发点:GLSurfaceView.onDetachFromWindow。也就是当调用 parent.removeView(glView) 再 parent.addView(glView) 时,同样可能触发 crash。
解决方法同样可以是直接在 onDetachFromWindow 时结束进程。对于大部分游戏来说,removeView 多发生在后台或者资源已回收的情况下,杀掉进程并重新启动并不会影响用户体验。
但在接入某些第三方 SDK 时,如果 SDK 在启动时将 root view 加入它们的容器中,就可能在启动时就被杀掉,这也是我们后来放弃在 2.x 版本接入该 SDK 的原因。Cocos 3.x 则没有这个问题。
可能的优化方向
可以尝试通过控制线程或 Render 的生命周期,复用之前的 Render 或线程,避免 Isolate 为空的情况。这个方向仍需要进一步实验。