前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

上一篇讲了窗口事件监听,那是检测应用切换的第一道防线。但窗口事件有时候不够可靠——某些场景下窗口失焦事件可能不触发或延迟触发。所以 secure_application 在 OpenHarmony 上实现了第二道防线:应用生命周期回调。

两套机制同时工作,确保用户离开 App 时一定能被检测到。

一、ApplicationStateChangeCallback 接口

1.1 接口定义

interface ApplicationStateChangeCallback {
  onApplicationForeground: () => void;
  onApplicationBackground: () => void;
}
回调 触发时机 说明
onApplicationForeground 应用从后台回到前台 用户切回 App
onApplicationBackground 应用从前台进入后台 用户切走 App

1.2 与窗口事件的区别

维度 Window 事件 生命周期回调
粒度 窗口级 应用级
下拉通知栏 ✅ 触发 ❌ 不触发
切到其他 App ✅ 触发 ✅ 触发
按 Home 键 ✅ 触发 ✅ 触发
系统弹窗 ✅ 触发 ❌ 不触发
可靠性

💡 互补关系:窗口事件更敏感(下拉通知栏也触发),生命周期回调更可靠(真正的前后台切换)。两者结合使用,既不遗漏也不过度触发。

二、registerLifecycleCallback 实现

2.1 完整代码

private applicationStateChangeCallback: ApplicationStateChangeCallback | null = null;

private registerLifecycleCallback(): void {
  if (this.context == null) {
    return;
  }
  try {
    const applicationContext = this.context.getApplicationContext();
    this.applicationStateChangeCallback = {
      onApplicationForeground: () => {
        Log.i(TAG, "Application came to foreground");
      },
      onApplicationBackground: () => {
        Log.i(TAG, "Application went to background");
        if (this.secured && this.channel != null) {
          this.channel.invokeMethod("lock", null);
        }
      }
    };
    applicationContext.on('applicationStateChange', this.applicationStateChangeCallback);
    Log.i(TAG, "Lifecycle callback registered");
  } catch (err) {
    Log.e(TAG, "Failed to register lifecycle callback: " + JSON.stringify(err));
  }
}

2.2 逐行分析

步骤 代码 说明
1 空值检查 context 为 null 时直接返回
2 getApplicationContext() 从 Context 获取 ApplicationContext
3 创建回调对象 实现 onApplicationForeground 和 onApplicationBackground
4 on(‘applicationStateChange’) 注册回调到 ApplicationContext
5 保存回调引用 用于后续注销

2.3 为什么保存回调引用

this.applicationStateChangeCallback = { ... };

注销时需要传入同一个回调对象

applicationContext.off('applicationStateChange', this.applicationStateChangeCallback);

如果不保存引用,就无法精确注销。

三、onApplicationBackground 的处理

3.1 核心逻辑

onApplicationBackground: () => {
  Log.i(TAG, "Application went to background");
  if (this.secured && this.channel != null) {
    this.channel.invokeMethod("lock", null);
  }
}

和窗口事件回调的逻辑完全一样:

  1. 检查是否开启了保护(this.secured
  2. 检查通道是否可用(this.channel != null
  3. 通知 Dart 层执行锁定

3.2 与窗口事件的重复触发

用户按 Home 键时,可能同时触发:

  • WINDOW_INACTIVE(窗口事件)
  • onApplicationBackground(生命周期回调)

两个回调都会调用 channel.invokeMethod("lock", null),Dart 层会收到两次 lock 通知。

但这不是问题,因为 Dart 层的 lockIfSecured() 有防重入逻辑:

void lockIfSecured() {
  if (value.secured) lock();
}

void lock() {
  if (!value.locked) {  // 已经锁定就不重复操作
    value = value.copyWith(locked: true);
    notifyListeners();
  }
}

📌 双重触发是安全的。宁可多触发一次(被防重入逻辑过滤),也不要漏触发。

四、onApplicationForeground 的处理

4.1 当前实现

onApplicationForeground: () => {
  Log.i(TAG, "Application came to foreground");
  // Flutter side handles the unlock flow via AppLifecycleState.resumed
}

前台回调只记录了日志,没有做任何操作。为什么?

4.2 为什么不在原生端处理前台恢复

解锁流程由 Dart 层 处理:

App 回到前台
    │
    ├── Flutter: AppLifecycleState.resumed
    │       │
    │       ▼
    │   _SecureApplicationState.didChangeAppLifecycleState
    │       │
    │       ├── 检查 secured && locked
    │       ├── 调用 onNeedUnlock(认证流程)
    │       └── 认证成功 → unlock
    │
    └── Native: onApplicationForeground
            │
            └── 只记录日志(不做操作)

原因:

  1. 认证逻辑在 Dart 层:onNeedUnlock 回调是 Dart Widget 的参数
  2. UI 在 Flutter 层:模糊遮罩是 Flutter Widget,不是原生 UI
  3. 避免冲突:如果原生端也执行 unlock,可能和 Dart 层的认证流程冲突

五、unregisterLifecycleCallback 注销

5.1 完整代码

private unregisterLifecycleCallback(): void {
  if (this.context == null || this.applicationStateChangeCallback == null) {
    return;
  }
  try {
    const applicationContext = this.context.getApplicationContext();
    applicationContext.off('applicationStateChange', this.applicationStateChangeCallback);
    this.applicationStateChangeCallback = null;
    Log.i(TAG, "Lifecycle callback unregistered");
  } catch (err) {
    Log.e(TAG, "Failed to unregister lifecycle callback: " + JSON.stringify(err));
  }
}

5.2 注销条件

条件 检查 原因
context != null 需要 context 获取 applicationContext
callback != null 需要传入同一个回调对象

5.3 注销后置空

this.applicationStateChangeCallback = null;

注销后将回调引用置为 null,防止:

  1. 重复注销
  2. 回调对象无法被 GC 回收

六、双保险机制的完整流程

6.1 用户按 Home 键

用户按 Home 键
    │
    ├── 窗口事件:WINDOW_INACTIVE
    │       │
    │       └── channel.invokeMethod("lock")  ← 第一次通知
    │
    └── 生命周期:onApplicationBackground
            │
            └── channel.invokeMethod("lock")  ← 第二次通知
                    │
                    ▼
            Dart: lockIfSecured()
                    │
                    ├── 第一次:locked=false → 执行锁定
                    └── 第二次:locked=true → 跳过(防重入)

6.2 用户下拉通知栏

用户下拉通知栏
    │
    ├── 窗口事件:WINDOW_INACTIVE
    │       │
    │       └── channel.invokeMethod("lock")  ← 触发锁定
    │
    └── 生命周期:不触发(App 没有真正进入后台)

6.3 各场景的触发情况

场景 窗口事件 生命周期 锁定次数
按 Home 键 2次(防重入过滤为1次)
切到其他 App 2次(防重入过滤为1次)
下拉通知栏 1次
系统弹窗 1次
应用内弹窗 0次

七、与 Android 生命周期的对比

7.1 Android 的做法

// Android:通过 Application.ActivityLifecycleCallbacks
application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
    override fun onActivityPaused(activity: Activity) {
        if (secured) channel?.invokeMethod("lock", null)
    }
    override fun onActivityResumed(activity: Activity) {
        // 不做操作,由 Flutter 层处理
    }
    // ... 其他回调
})

7.2 对比

维度 Android OpenHarmony
注册对象 Application ApplicationContext
注册方法 registerActivityLifecycleCallbacks on(‘applicationStateChange’)
注销方法 unregisterActivityLifecycleCallbacks off(‘applicationStateChange’)
回调粒度 Activity 级(6个回调) 应用级(2个回调)
是否需要保存引用

7.3 OpenHarmony 的简洁性

Android 的 ActivityLifecycleCallbacks 有6个回调方法(created, started, resumed, paused, stopped, destroyed),大部分用不到。OpenHarmony 只有2个回调(foreground, background),更简洁。

// OpenHarmony:只需要关心前台和后台
{
  onApplicationForeground: () => { ... },
  onApplicationBackground: () => { ... }
}

💡 设计评价:OpenHarmony 的 API 设计更贴合实际需求。对于 secure_application 这样的场景,我们只关心"App 是否在前台",不需要知道 Activity 的详细生命周期。

八、注册顺序与初始化时序

8.1 onAttachedToEngine 中的初始化顺序

onAttachedToEngine(binding: FlutterPluginBinding): void {
  // 1. 创建 MethodChannel
  this.channel = new MethodChannel(binding.getBinaryMessenger(), "secure_application");
  this.channel.setMethodCallHandler(this);

  // 2. 获取上下文
  this.context = binding.getApplicationContext();

  // 3. 获取窗口(异步)+ 注册窗口事件
  this.getMainWindow();

  // 4. 注册生命周期回调(同步)
  this.registerLifecycleCallback();
}

8.2 为什么这个顺序

  1. 先创建 Channel:后续的回调需要通过 Channel 通知 Dart 层
  2. 再获取上下文:窗口获取和生命周期注册都需要上下文
  3. 窗口获取是异步的:不阻塞后续初始化
  4. 生命周期注册是同步的:立即生效

总结

本文详细讲解了应用生命周期回调的注册与使用:

  1. ApplicationStateChangeCallback:两个回调,foreground 和 background
  2. 双保险机制:窗口事件 + 生命周期回调,确保不遗漏
  3. 防重入:Dart 层的 lock() 自动过滤重复通知
  4. 前台恢复:由 Dart 层处理,原生端不干预
  5. 注销清理:保存回调引用,精确注销

下一篇我们讲 MethodChannel 通信协议的完整设计——把 Dart 层和原生层的所有通信都梳理清楚。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

请添加图片描述

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐