Ionic iOS应用含ThreeJS/PIXI场景页面跳转后WKWebView重载问题求助
基于Ionic开发的iOS应用,使用ThreeJS和PIXI实现3D/2D场景功能,选择语言后可在搭载场景的页面间跳转交互。随着场景页面进入次数增加,WKWebView触发重载的概率逐步上升(对应回调webViewWebContentProcessDidTerminate),复现概率和设备型号、页面跳转速度正相关。
业务逻辑如下:从CouchBase获取数据后,ThreeJS组件加载2MB12MB的glb文件,部分场景额外加载3MB5MB的360°背景图加入场景;部分场景使用WebGlTargetRenderer,所有场景均搭载OrbitControls实例;采用共享渲染器避免创建过多WebGL上下文,组件销毁时会执行资源清理逻辑。
性能分析结果:页面跳转时内存时间线峰值约1MB后回落,仅JavaScript分配的ArrayBuffer大小持续增长,内存占用明显高于未创建ThreeJS组件的状态。
Xcode日志表现:内存警告和WKWebView重载无对应关系,有时弹出警告后重载,有时弹出警告也不重载,有时无警告也会触发重载,已确认报错和RBS Background assertion ConnectionTerminationWatchdog错误完全一致。
/// HELPERS const disposeAll = (object: any) => { if (!object.isMesh) return; object.geometry.dispose(); if (object.material.isMaterial) { cleanMaterial(object.material); } else { for (const material of object.material) cleanMaterial(material); } } const cleanMaterial = material => { material.dispose(); if (material.map) { material.map.dispose(); material.map = null; } material.needsUpdate = true; for (const key of Object.keys(material)) { const value = material[key] if(value?.dispose && typeof value.dispose === "function"){ console.log("DISPOSING", value) value.dispose() } if (value && typeof value === 'object' && 'minFilter' in value) { value.dispose() } } } /////NGDESTROY ngOnDestroy() { clearInterval(this.interval); cancelAnimationFrame(this.requestId); this.resourceTracker.dispose(); this.controls.dispose(); this.scene.traverse(disposeAll); this.webGl.reset(); // RENDERER SERVICE this.scene = null; } ///// RENDERER SERVICE reset() { this.renderer.dispose() this.renderer.renderLists.dispose() this.renderer.xr.dispose() this.renderer.state.reset() this.renderer.clear() this.renderer.properties.dispose() }
- 调用
this.zone.runOutsideAngular( () => this.animate() )执行动画逻辑 - 使用加载管理器按顺序加载所有glb文件和背景资源:
await new GLTFLoader(manager(this.loadTransitionPolarizedModel.bind(this))).loadAsync(this.modelUrl);(优化效果不明显) - 使用单个共享渲染器,按照官方规范清理ThreeJS场景
- 使用单个共享canvas
- 针对ArrayBuffer增长问题修改本地ThreeJS源码、尝试社区相关清理方案,均无效
- 使用共享Scene
- 开发Cordova插件清理缓存、释放内存
- 移除glb模型后应用不再崩溃
- 加载glb模型但不加入场景,初期运行正常,后期还是会崩溃;更换更小的glb文件且不加入场景,反复跳转页面10分钟都运行正常
- 移除
renderer.setPixelRatio和renderer.setSize调用,无改善
内存相关排查
- 补充资源清理覆盖范围:当前
disposeAll逻辑仅处理Mesh类型,需补充适配Line、Points、Sprite等其他可渲染对象,同时检查WebGlTargetRenderer创建的渲染目标、纹理缓存是否完全释放,OrbitControls绑定的DOM事件是否完全移除 - 手动触发GC验证泄漏:在Safari调试工具的Memory面板中,每次退出场景后手动执行垃圾回收,对比ArrayBuffer、GPU内存的变化,确认是否有未被引用的资源仍被持有
- 验证PIXI资源释放逻辑:当前应用同时使用ThreeJS和PIXI,需确认PIXI的纹理、渲染器、容器是否在页面销毁时同步执行
dispose,避免双引擎的资源泄漏叠加
线程与性能相关排查
- 排查长任务:用Safari调试工具的Timeline面板,统计页面跳转、资源加载、渲染阶段的主线程任务耗时,确认是否存在单次超过100ms的长任务,iOS WebKit对长时间阻塞主线程的进程会直接触发终止
- 优先落地glb加载入Worker的方案:glb解析过程会产生大量临时ArrayBuffer,放到Worker中处理后仅传递序列化后的几何、材质数据到主线程,可大幅减少主线程内存波动和临时对象持有
- 动态降级资源规格:对于大尺寸360°背景图,可根据设备性能动态降级分辨率,比如A11以下设备使用2048*1024分辨率的背景,避免GPU内存占用过高触发WebKit的隐形限制
系统特性适配
- 调整WKWebView配置:可直接修改cordova-plugin-ionic-webview插件源码的
WKWebViewConfiguration配置项,尝试禁用GPU进程合并、增大Web进程可用内存阈值 - 验证不同iOS版本表现:该RBS看门狗错误在iOS 14-15版本上触发概率更高,可验证iOS 16以上版本的复现情况,确认是否为系统版本的已知问题
使用Xcode的Instruments工具可从底层定位问题:
- 用GPU Counter工具统计每次进入/退出场景后的GPU内存占用、纹理数、顶点缓冲区占用,确认GPU侧是否存在资源泄漏
- 用WebKit Debugging工具查看WebContent进程的内存、CPU占用变化,对比系统其他进程的资源占用情况,确认是否是系统资源紧张触发的进程终止
- 开启WKWebView的运行时日志,添加
WKWebViewConfiguration的preferences.setValue(true, forKey: "developerExtrasEnabled")配置,可输出更详细的WebContent进程崩溃原因
不可行。ThreeJS依赖DOM的Canvas元素和WebGL上下文,Cordova插件运行在原生独立线程,无法直接访问WKWebView内的DOM和WebGL实例,也无法直接渲染内容到页面上。仅可通过插件实现离线资源预加载、大文件解码等非渲染类逻辑,辅助降低主线程压力。
内容的提问来源于stack exchange,提问作者Paolo

