onBackPress 中必须在函数入口立即 return true 拦截返回,再异步弹窗确认;双击退出需手动记录时间戳并挂载到 this;H5 不触发 onBackPress,需用 history.back() 降级。

onBackPress 里 return true 是硬性要求
不 return true,系统就默认放行返回逻辑,uni.showModal 弹出来还没点确认,页面已经退掉了。必须在事件回调最开头就拦截,哪怕后续要等用户操作再决定是否真退出。
常见错误是把 return true 写在 success 回调里,或者只在某个分支里 return,导致其他路径没拦截:
- ❌ 错误写法:
if (res.confirm) { plus.runtime.quit(); } return true;—— 这样无论用户点确定还是取消,都拦截了,但取消后没做任何事,体验卡死 - ✅ 正确写法:函数入口立刻
return true,然后异步处理弹窗和后续动作
双击退出必须自己记时间戳,uni-app 不提供内置双击检测
onBackPress 每次触发都是独立事件,没有“上一次触发时间”这种上下文。你要自己用变量存时间戳,判断间隔是否小于 2000ms。
注意点:
- 变量必须挂载在页面 data 或 Vue 实例上(比如
this.lastBackTime),不能用局部变量,否则每次触发都是新值 - H5 平台不走
onBackPress,所以双击逻辑只对 App 有效;鸿蒙需用uts插件调context.terminateSelf(),不能直接用plus.runtime.quit() - 如果用户从子页返回首页后双击,首页应退出;但若首页是 tabbar 页面,
uni.navigateBack()会失败,此时应 fallback 到plus.runtime.quit()
H5 页面刷新后返回键失效,要用 history.back() 降级
刷新后 getCurrentPages() 返回空数组,uni.navigateBack() 静默失败,用户按返回键没反应。这不是 bug,是浏览器 history 和 uni-app 页面栈天然不一致导致的。
判断并切换方案:
- 先查平台:
uni.getSystemInfoSync().platform === 'h5' - 再查栈长:
getCurrentPages().length - 两者同时为真时,改用
history.back(),它不依赖 uni-app 栈,直接操作浏览器历史记录 - 不要试图手动 pushState 补页面栈,容易和路由系统冲突,且无法还原页面状态
APP 端隐藏到后台比直接 quit 更稳妥
很多安卓厂商限制应用自启或杀后台,plus.runtime.quit() 后用户再点图标可能冷启动,体验反而差。更合理的是调用 main.moveTaskToBack(false) 把当前任务移到后台。
实操细节:
- 代码必须放在
#ifdef APP-PLUS块内,避免 H5 或小程序报错 - 要重写
plus.runtime.quit方法,而不是只在onBackPress里调一次,否则其他地方(比如原生菜单)仍会真正退出 - 快速连按两次返回时,第二次触发前需确保第一次已把 App 移入后台,否则可能因 activity 不存在而 crash
双击退出看似简单,但跨平台行为差异大,尤其是 H5 的 history 机制和 App 的 activity 生命周期完全不在一个抽象层上。最容易被忽略的是:**H5 刷新后页面栈为空是正常现象,强行补栈不如接受它、换用原生能力**。











