Flutter三方库适配OpenHarmony【secure_application】— 应用生命周期回调注册
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net上一篇讲了窗口事件监听,那是检测应用切换的第一道防线。但窗口事件有时候不够可靠——某些场景下窗口失焦事件可能不触发或延迟触发。所以 secure_application 在 OpenHarmony 上实现了第二道防线:应用生命周期回调。两套机制同时工作,确保用户离开 App 时一定能被检测
前言
欢迎加入开源鸿蒙跨平台社区: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);
}
}
和窗口事件回调的逻辑完全一样:
- 检查是否开启了保护(
this.secured) - 检查通道是否可用(
this.channel != null) - 通知 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
│
└── 只记录日志(不做操作)
原因:
- 认证逻辑在 Dart 层:onNeedUnlock 回调是 Dart Widget 的参数
- UI 在 Flutter 层:模糊遮罩是 Flutter Widget,不是原生 UI
- 避免冲突:如果原生端也执行 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,防止:
- 重复注销
- 回调对象无法被 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 为什么这个顺序
- 先创建 Channel:后续的回调需要通过 Channel 通知 Dart 层
- 再获取上下文:窗口获取和生命周期注册都需要上下文
- 窗口获取是异步的:不阻塞后续初始化
- 生命周期注册是同步的:立即生效
总结
本文详细讲解了应用生命周期回调的注册与使用:
- ApplicationStateChangeCallback:两个回调,foreground 和 background
- 双保险机制:窗口事件 + 生命周期回调,确保不遗漏
- 防重入:Dart 层的 lock() 自动过滤重复通知
- 前台恢复:由 Dart 层处理,原生端不干预
- 注销清理:保存回调引用,精确注销
下一篇我们讲 MethodChannel 通信协议的完整设计——把 Dart 层和原生层的所有通信都梳理清楚。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- ApplicationStateChangeCallback 文档
- ApplicationContext.on 文档
- Android ActivityLifecycleCallbacks
- secure_application OpenHarmony 源码
- Flutter AppLifecycleState
- OpenHarmony 应用模型
- ArkTS 异步编程
- 开源鸿蒙跨平台社区

更多推荐

所有评论(0)