适用场景:
- Web 页无法正确响应前后台切换(音乐不停、计时器不暂停等)。
- 按返回键时要么直接退出 App,要么没有任何反应。
本文基于本仓库的实现,系统梳理 HarmonyOS + Cordova 的生命周期与返回键链路,并给出常见问题的排查思路与示例代码(代码约占 3/10)。
1. 整体调用链先看清
1.1 生命周期 & 返回键调用链概览
从用户操作到 Web 页的事件回调,大致经历以下链路:
用户 Index.ets cordova.Index.ets Cordova Core Web(index.html/app.js) 页面进入前台/后台, 按返回键 pageShowEvent / pageHideEvent / pageBackPress onArkTsResult(JSON, 'CoreHarmony', '') 触发 pause/resume/backbutton 等事件 游戏暂停/恢复,处理返回 用户 Index.ets cordova.Index.ets Cordova Core Web(index.html/app.js)
理解这条链路后,我们就知道:
- 如果 Index.ets 没调用
page*函数,Web 就收不到事件。 - 如果 Cordova Core 未正确分发,事件也肯能中断。
- 如果 Web 侧没监听事件,自然也看不到效果。
2. ArkTS 侧:Index.ets 的生命周期透传
2.1 标准写法回顾
ts
// entry/src/main/ets/pages/Index.ets
import {
MainPage,
pageBackPress,
pageHideEvent,
pageShowEvent,
PluginEntry
} from '@magongshou/harmony-cordova/Index';
@Entry
@Component
struct Index {
cordovaPlugs: Array<PluginEntry> = [];
/** 页面显示生命周期:通知 Cordova 页面已显示 */
onPageShow() {
pageShowEvent();
}
/** 返回键拦截:交由 Cordova 处理返回栈 */
onBackPress() {
pageBackPress();
return true; // 返回 true 拦截默认行为
}
/** 页面隐藏生命周期:通知 Cordova 页面已隐藏 */
onPageHide() {
pageHideEvent();
}
build() {
RelativeContainer() {
MainPage({
isWebDebug: false,
cordovaPlugs: this.cordovaPlugs
});
}
.height('100%')
.width('100%')
}
}
2.2 常见错误 & 排查
- 忘记实现
onPageShow/onPageHide- 结果:Web 收不到
resume/pause,前后台切换时状态不对。
- 结果:Web 收不到
onBackPress未返回true- 结果:系统默认行为生效(直接退出),Cordova 无法接管返回键。
- 导包错误
- 例如未从
@magongshou/harmony-cordova/Index导入pageBackPress函数,导致调用的是其它同名方法或编译失败。
- 例如未从
建议在开发版加上简单日志,快速确认 Index 的生命周期是否被触发:
ts
onBackPress() {
console.log('[Index] onBackPress');
pageBackPress();
return true;
}
3. Cordova 入口:page* 函数到 Core 的映射
ArkTS 侧的 pageShowEvent/pageHideEvent/pageBackPress 本质是一个轻量包装:
ts
// cordova/Index.ets(简化示意)
import cordova from 'libcordova.so'
import { ArkTsAttribute } from './src/main/ets/components/PluginGlobal';
export function pageShowEvent() {
let result: ArkTsAttribute = {content: 'resume', result: []};
cordova.onArkTsResult(JSON.stringify(result), 'CoreHarmony', '');
}
export function pageHideEvent() {
let result: ArkTsAttribute = {content: 'pendingPause', result: []};
cordova.onArkTsResult(JSON.stringify(result), 'CoreHarmony', '');
}
export function pageBackPress() {
let result: ArkTsAttribute = {content: 'overrideBackbutton', result: []};
cordova.onArkTsResult(JSON.stringify(result), 'CoreHarmony', '');
}
要点:
content字段是 Cordova Core 用来判断事件类型的关键字:resume→ 转成 Web 侧的resume事件。pendingPause→ 对应pause相关处理。overrideBackbutton→ 转成backbutton事件。
onArkTsResult的第二个参数'CoreHarmony'用来指明目标 service。
3.1 如何判断 onArkTsResult 是否成功调用?
- 在 native 日志中搜索关键字:
onArkTsResult、resume、overrideBackbutton等。
- 若完全没有相关日志,很可能:
- Index 没有调用
page*。 - 编译时链接
libcordova.so失败(通常会有更明显的错误)。
- Index 没有调用
4. Web 侧:事件监听是否正确
即使原生侧一切正常,如果 Web 代码没有监听 pause/resume/backbutton,也不会有任何效果。
4.1 建议的事件监听模板
假设你的 index.html 引入了 cordova.js,可以在 app.js 中添加:
js
// app.js 中
function onDeviceReady() {
document.addEventListener('pause', onPause, false);
document.addEventListener('resume', onResume, false);
document.addEventListener('backbutton', onBackButton, false);
}
document.addEventListener('deviceready', onDeviceReady, false);
function onPause() {
console.log('[2048] onPause');
// 在这里暂停音乐、保存游戏状态等
}
function onResume() {
console.log('[2048] onResume');
// 在这里恢复音乐、恢复计时器等
}
function onBackButton(e) {
console.log('[2048] backbutton');
// 自定义返回行为,例如弹出确认框
e.preventDefault();
}
4.2 常见问题
deviceready未触发- 可能原因:
cordova.js未正确加载。- Cordova 框架初始化失败(可结合其他日志排查)。
- 可能原因:
- 多次注册监听器
- 容易导致重复处理,例如 backbutton 被处理多次;建议在单一入口(如
onDeviceReady)注册。
- 容易导致重复处理,例如 backbutton 被处理多次;建议在单一入口(如
5. 常见问题场景与排查示例
场景 1:按返回键直接退出 App,而不是关闭当前弹窗
期望行为:
- 在游戏中打开设置弹窗,按返回键应该先关闭弹窗,而不是直接退出 App。
排查 Checklist:
- Index.ets 的
onBackPress是否返回了true? - Web 侧是否监听了
backbutton并阻止默认行为?
参考实现:
ts
// Index.ets 中
onBackPress() {
console.log('[Index] onBackPress');
pageBackPress();
return true; // 拦截系统默认返回
}
js
// app.js 中
let dialogOpen = false;
function openSettingsDialog() {
dialogOpen = true;
// 展示设置弹窗
}
function closeSettingsDialog() {
dialogOpen = false;
// 关闭设置弹窗
}
function onBackButton(e) {
if (dialogOpen) {
e.preventDefault();
closeSettingsDialog();
} else {
// 根据需求决定是否退出或二次确认
if (confirm('确定退出游戏吗?')) {
navigator.app.exitApp && navigator.app.exitApp();
} else {
e.preventDefault();
}
}
}
场景 2:切到后台再回来,音乐与动画依然在后台继续跑
排查思路:
Index.onPageHide/onPageShow是否被调用?- Cordova 是否收到
pendingPause/resume? - Web 是否监听了
pause/resume事件?
建议做法:
- 在
onPause中统一做:- 暂停音乐。
- 停止动画定时器或
requestAnimationFrame。 - 保存游戏状态到
localStorage。
- 在
onResume中统一恢复。
js
let musicPlayer = {/* 伪代码:音乐播放对象 */};
let timerId = 0;
function startGameLoop() {
timerId = setInterval(step, 1000 / 60);
}
function stopGameLoop() {
clearInterval(timerId);
}
function onPause() {
console.log('[2048] onPause - stop loop & music');
stopGameLoop();
musicPlayer.pause && musicPlayer.pause();
}
function onResume() {
console.log('[2048] onResume - resume loop & music');
startGameLoop();
musicPlayer.play && musicPlayer.play();
}
6. 调试思路:从 Log 到 Web 控制台
为了快速定位生命周期与返回键问题,可以构建一套"多层日志":
Index.ets 日志 Cordova onArkTsResult 日志 Web console.log 日志 UI 行为是否符合预期
- Index 层 :
console.log('[Index] onPageShow/onPageHide/onBackPress')。
- Cordova Core 层 (需要阅读 native 日志):
- 搜索
resume/pendingPause/overrideBackbutton。
- 搜索
- Web 层 :
console.log('[2048] onPause/onResume/backbutton')。
通过对比三层日志:
- 如果 Index 有日志,Web 没有 → 问题在 Cordova Core 或 Web 事件监听上。
- 如果 Index 没日志 → 问题在 ArkTS 生命周期绑定上。
7. 总结:生命周期与返回键的排查路线图
最后,用一张图帮你形成"肌肉记忆":
否 是 否 是 否 是 否 是 生命周期/返回键异常 Index.ets 是否实现 onPageShow/onPageHide/onBackPress? 补充实现, 调用 page* 函数 onBackPress 是否 return true? 返回 true 拦截系统返回 Web 是否监听 pause/resume/backbutton? 在 deviceready 中注册事件 日志三层是否对齐? 结合 native & Web 日志进一步定位 检查插件/业务代码逻辑
有了这套排查方法,你在实际项目中遇到:
- "切到后台音乐不停"、
- "按返回键直接退出"、
- "Web 页似乎没收到暂停/恢复"
时,都可以沿着 Index → Cordova → Web 这条主线一步步定位,而不用盲目地在各处加 alert/console.log 试运气。