上一篇文章聊了生命周期和启动框架,这次来说说应用怎么结束。别小看这个问题:用户按返回键该退出还是挂后台?应用崩溃怎么查原因?有些场景需要应用自己重启怎么搞?处理不好用户真的会骂人。
我之前踩过一个坑:用户反馈“改了语言,重启应用又变回去”。查了半天发现应用退出时数据没持久化,下次启动又从默认配置读。后来才搞清楚onDestroy在某些场景下根本不走,代码白写了。
这篇就把应用退出、异常原因排查、应用重启三块串起来讲,都是实战一定会遇到的东西。
一、应用退出:不止一种方式
鸿蒙应用的退出机制分三大类,每一类处理方式不同。
1. 用户主动退出
最常见的是返回键退出。默认按返回键只是把UIAbility挂到后台,不销毁。如果想直接退出,可以重写onBackPressed方法:- import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- export default class EntryAbility extends UIAbility {
- onBackPressed(): boolean {
- this.context.terminateSelf().then(() => {
- hilog.info(0x0000, 'Demo', 'terminateSelf成功');
- }).catch((err: BusinessError) => {
- hilog.error(0x0000, 'Demo', `terminateSelf失败: ${err.code}, ${err.message}`);
- });
- return true; // 返回true表示消费事件,不再默认挂后台
- }
- }
复制代码 注意:onBackPressed返回true表示事件被消费,系统不做默认处理;返回false则交给系统挂后台。
多任务清理(上滑卡片或点清除全部)时,onDestroy是保证触发的,可以在这里做数据保存。但在Dock栏关闭应用(PC或平板)时,onDestroy不保证触发,千万别依赖它保存关键数据。
2. 应用主动退出
通过代码调用terminateSelf,场景包括检测到致命错误主动终止、完成核心任务后自动关闭等。系统会触发onDestroy,走正常生命周期销毁。- import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- export default class EntryAbility extends UIAbility {
- private doExit(): void {
- this.context.terminateSelf().then(() => {
- console.info('应用主动退出成功');
- }).catch((err: BusinessError) => {
- console.error(`退出失败: ${err.code}, ${err.message}`);
- });
- }
- }
复制代码
3. 系统强制终止
这类情况最坑爹——不会触发onDestroy。包括JavaScript崩溃、C++空指针异常、系统内存紧张回收进程、温度过高保护、应用升级时清理旧进程等。你的关键数据保存逻辑如果写在onDestroy里,碰上系统强杀等于白写。
那怎么办?关键数据要实时持久化,别等到退出时才存。
二、获取异常退出原因
既然系统强杀不通知,那至少得知道上次为什么挂了吧。鸿蒙从API 9开始就在LaunchParam里提供了lastExitReason,从API 18开始还能拿到更详细的内存信息。
在UIAbility的onCreate里读取:- import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
- export default class MyAbility extends UIAbility {
- onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- const reason = launchParam.lastExitReason;
- const exitMsg = launchParam.lastExitMessage;
- console.info(`上次退出原因: ${reason}, 详情: ${exitMsg}`);
- if (reason === AbilityConstant.LastExitReason.JS_ERROR) {
- // 触发崩溃上报
- } else if (reason === AbilityConstant.LastExitReason.RESOURCE_CONTROL) {
- // 检查是否需要清理缓存
- }
- }
- }
复制代码
注意:PERFORMANCE_CONTROL(值6)已废弃,官方建议用RESOURCE_CONTROL替代。
LastExitDetailInfo(API 18+)可以获取退出时的进程状态:- import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
- export default class MyAbility extends UIAbility {
- onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- if (launchParam.lastExitDetailInfo) {
- const info = launchParam.lastExitDetailInfo;
- console.info(`PID: ${info.pid}, 进程名: ${info.processName}`);
- console.info(`RSS: ${info.rss} KB, PSS: ${info.pss} KB`);
- // 如果RSS或PSS过高,大概率是被内存管控杀的
- if (info.rss > 500000) { // 500MB
- console.warn('内存占用过高,建议优化');
- }
- }
- }
- }
复制代码 exitSubReason也有很多门道:100~102是户外模式相关,103是后台CPU高负载,107/108是内存超限。看到107基本可以确定是PSS内存超限杀的。
实战中我们可以做一个崩溃检测和上报:- import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
- export default class EntryAbility extends UIAbility {
- onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- this.checkLastExit(launchParam);
- }
- private checkLastExit(launchParam: AbilityConstant.LaunchParam): void {
- const reason = launchParam.lastExitReason;
- if (reason === AbilityConstant.LastExitReason.NORMAL || reason === AbilityConstant.LastExitReason.UNKNOWN) {
- return;
- }
- console.warn(`异常退出,原因: ${reason}, 消息: ${launchParam.lastExitMessage}`);
- if (launchParam.lastExitDetailInfo) {
- const info = launchParam.lastExitDetailInfo;
- if (info.rss > 400000 || info.pss > 300000) {
- console.warn(`内存异常: RSS=${info.rss}KB, PSS=${info.pss}KB`);
- }
- }
- this.reportExitReason(reason, launchParam.lastExitMessage);
- }
- private reportExitReason(reason: number, message: string): void {
- // 上报到统计分析平台
- }
- }
复制代码
三、应用重启:什么时候需要自己重启
有些场景需要主动重启:下载新资源需重新加载、关键配置变化需从初始状态开始、应用状态出问题想刷新。鸿蒙从API 12开始提供两种主动重启方式。
不保留窗口的重启:用ApplicationContext.restartApp,API 12+。重启过程中当前窗口关闭,用户会看到桌面再拉起。适合不介意闪一下的场景。- import { common, Want } from '@kit.AbilityKit';
- function restartApp(context: common.ApplicationContext): void {
- let want: Want = {
- bundleName: 'com.example.myapp',
- abilityName: 'EntryAbility'
- };
- try {
- context.restartApp(want);
- } catch (err) {
- console.error(`重启失败: ${err.code}, ${err.message}`);
- }
- }
复制代码
保留窗口的重启:用UIAbilityContext.restartApp,API 22+。重启过程中窗口保留,体验更连贯。- import { common, Want } from '@kit.AbilityKit';
- async function restartWithWindow(context: common.UIAbilityContext): Promise<void> {
- let want: Want = {
- bundleName: 'com.example.myapp',
- abilityName: 'EntryAbility'
- };
- try {
- await context.restartApp(want);
- } catch (err) {
- console.error(`重启失败: ${err.code}, ${err.message}`);
- }
- }
复制代码 如果重启后想跳转到另一个页面,把abilityName改成其他Ability即可。
两种方式共同的限制:只能在主线程调用,应用必须处于焦点状态,3秒内不能重复调用。后台应用不能自己重启,我的做法是先切到前台再调,或者给用户弹提示。
元服务重启:用abilityManager.restartSelfAtomicService,API 20+,支持元服务的免安装更新。
此外还有被动重启:appRecovery模块。开启后应用挂了系统自动恢复并重启Ability,甚至能恢复到崩溃前的页面状态。- import { appRecovery } from '@kit.AbilityKit';
- export default class EntryAbility extends UIAbility {
- onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- appRecovery.enableAppRecovery(
- appRecovery.RestartFlag.ALWAYS_RESTART,
- appRecovery.SaveOccasionFlag.SAVE_OCCASION_UI_STATE
- );
- }
- }
复制代码 注意:如果崩溃是脏数据导致的,自动恢复后会再次崩溃,可能死循环,不适合所有场景。
四、实际踩过的坑
坑1:onDestroy做保存,系统强杀全丢了
我之前把主题色保存逻辑写在onDestroy里,结果系统内存紧张直接杀进程,用户改完颜色再打开还是旧的。正确做法是数据改了立刻存,不依赖onDestroy。
坑2:restartApp在子线程调用直接报错
异步任务里调restartApp,报错:非主线程无权限。后来改用EventHub发消息到主线程调。
坑3:LastExitDetailInfo为空
这个字段从API 18开始,而且是可选的,只有异常退出时才可能有。取值时一定要做空判断。
坑4:SIGNAL退出的迷惑行为
看到大量lastExitReason === SIGNAL(值10)不要慌,这表示系统发kill信号,但不一定是坏事(如系统整理内存、应用升级)。要结合exitSubReason或时间戳判断。
坑5:3秒内重复调用restartApp被忽略
解决方案是UI层做防抖,保存上次调用时间,小于4秒就忽略。
总结
应用退出这件事坑不少,几个要点:用户主动退出走onDestroy可放心做清理;系统强制终止不触发onDestroy,关键数据要实时持久化;lastExitReason+lastExitDetailInfo是排查崩溃的利器,但注意空判断;主动重启分两种:流畅体验用UIAbilityContext.restartApp(API 22+),不在意闪一下用ApplicationContext.restartApp(API 12+);重启接口需在主线程调用,3秒内不能重复,应用必须在前台。
最后,建议定期查看异常退出数据。我们上线上报功能后才发现,大量退出是因为内存问题,直接推动了内存优化工作。 |