Flutter三方库适配OpenHarmony【secure_application】— 应用生命周期状态机全解析
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net前几篇我们从原生端的角度看了窗口事件和生命周期回调。这一篇换个视角,从Dart 层看的完整逻辑。这段代码是 secure_application 最核心的业务逻辑——它决定了什么时候锁定、什么时候触发认证、什么时候解锁。说实话,第一次读这段代码的时候我也花了不少时间才理清所有分支。今天把它
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
前几篇我们从原生端的角度看了窗口事件和生命周期回调。这一篇换个视角,从 Dart 层看 didChangeAppLifecycleState 的完整逻辑。这段代码是 secure_application 最核心的业务逻辑——它决定了什么时候锁定、什么时候触发认证、什么时候解锁。
说实话,第一次读这段代码的时候我也花了不少时间才理清所有分支。今天把它彻底拆解。
一、AppLifecycleState 四种状态
1.1 状态定义
enum AppLifecycleState {
resumed, // 应用在前台,可见且可交互
inactive, // 应用可见但不可交互(如来电、下拉通知栏)
paused, // 应用不可见(进入后台)
detached, // 应用即将销毁
}
1.2 状态转换
resumed
↗ ↘
inactive paused
↘ ↗
detached
| 用户操作 | 状态变化 |
|---|---|
| 正常使用 App | resumed |
| 下拉通知栏 | resumed → inactive |
| 收起通知栏 | inactive → resumed |
| 按 Home 键 | resumed → inactive → paused |
| 切回 App | paused → inactive → resumed |
| 杀掉 App | paused → detached |
1.3 在 OpenHarmony 上的行为
| 操作 | Android | OpenHarmony |
|---|---|---|
| 按 Home 键 | resumed→inactive→paused | resumed→inactive→paused |
| 打开最近任务 | resumed→inactive | resumed→inactive |
| 切到其他 App | resumed→inactive→paused | resumed→inactive→paused |
| 下拉通知栏 | resumed→inactive | resumed→inactive |
📌 OpenHarmony 上的 AppLifecycleState 行为与 Android 基本一致,因为 Flutter-OHOS 框架做了对齐。
二、didChangeAppLifecycleState 完整逻辑
2.1 源码
void didChangeAppLifecycleState(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.resumed:
if (mounted) {
setState(() => _removeNativeOnNextFrame = true);
} else {
_removeNativeOnNextFrame = true;
}
if (!secureApplicationController.paused) {
if (secureApplicationController.secured &&
secureApplicationController.value.locked) {
if (widget.onNeedUnlock != null) {
secureApplicationController.pause();
var authStatus = await widget.onNeedUnlock!(secureApplicationController);
if (authStatus != null) {
secureApplicationController.sendAuthenticationEvent(authStatus);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
secureApplicationController.unpause();
});
}
}
secureApplicationController.resumed();
}
super.didChangeAppLifecycleState(state);
break;
case AppLifecycleState.inactive:
if (!secureApplicationController.paused) {
if (secureApplicationController.secured) {
secureApplicationController.lock();
}
}
super.didChangeAppLifecycleState(state);
break;
case AppLifecycleState.paused:
if (!secureApplicationController.paused) {
if (secureApplicationController.secured) {
secureApplicationController.lock();
}
}
super.didChangeAppLifecycleState(state);
break;
default:
super.didChangeAppLifecycleState(state);
break;
}
}
三、inactive / paused 分支:锁定逻辑
3.1 代码
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
if (!secureApplicationController.paused) {
if (secureApplicationController.secured) {
secureApplicationController.lock();
}
}
break;
3.2 决策树
AppLifecycleState.inactive 或 paused
│
├── controller.paused == true?
│ └── YES → 什么都不做(暂停状态,跳过锁定)
│
└── controller.paused == false?
│
├── controller.secured == true?
│ └── YES → controller.lock() → 显示模糊遮罩
│
└── controller.secured == false?
└── 什么都不做(保护未开启)
3.3 两个条件的含义
| 条件 | 含义 | 为 false 时 |
|---|---|---|
!paused |
没有被暂停 | 正在进行文件选择等操作,不应锁定 |
secured |
保护已开启 | 用户没有开启保护,不需要锁定 |
3.4 inactive 和 paused 的处理相同
两个状态用了完全相同的逻辑。为什么?
因为用户按 Home 键时,状态变化是 resumed → inactive → paused。如果只在 paused 时锁定,那么在 inactive 阶段(大约几百毫秒)用户可能在应用切换器中看到未保护的内容。
在 inactive 时就锁定,可以尽早保护内容。
四、resumed 分支:解锁与认证
4.1 第一部分:原生遮罩移除
case AppLifecycleState.resumed:
if (mounted) {
setState(() => _removeNativeOnNextFrame = true);
} else {
_removeNativeOnNextFrame = true;
}
设置标志位,在下一次 build 时移除原生端的保护(主要针对 iOS 的原生模糊视图)。
4.2 第二部分:认证流程
if (!secureApplicationController.paused) {
if (secureApplicationController.secured &&
secureApplicationController.value.locked) {
if (widget.onNeedUnlock != null) {
secureApplicationController.pause();
var authStatus = await widget.onNeedUnlock!(secureApplicationController);
if (authStatus != null) {
secureApplicationController.sendAuthenticationEvent(authStatus);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
secureApplicationController.unpause();
});
}
}
secureApplicationController.resumed();
}
4.3 认证流程决策树
AppLifecycleState.resumed
│
├── controller.paused == true?
│ └── YES → 什么都不做
│
└── controller.paused == false?
│
├── secured && locked?
│ │
│ ├── onNeedUnlock != null?
│ │ │
│ │ ├── YES → 执行认证流程
│ │ │ ├── pause()(防止认证过程中再次触发)
│ │ │ ├── await onNeedUnlock()
│ │ │ ├── sendAuthenticationEvent()
│ │ │ └── unpause()(恢复正常)
│ │ │
│ │ └── NO → 不认证(遮罩保持显示)
│ │
│ └── resumed()(通知监听者)
│
└── !secured || !locked?
└── resumed()(通知监听者)
4.4 pause/unpause 的妙用
secureApplicationController.pause();
var authStatus = await widget.onNeedUnlock!(secureApplicationController);
// ...
WidgetsBinding.instance.addPostFrameCallback((_) {
secureApplicationController.unpause();
});
认证过程中先 pause(),认证完成后 unpause()。为什么?
因为认证过程可能涉及跳转到其他页面(比如系统的生物识别界面),这会再次触发 inactive 状态。如果不 pause,就会再次锁定,导致认证流程被中断。
| 时间线 | 没有 pause | 有 pause |
|---|---|---|
| t0: resumed | 开始认证 | 开始认证 + pause |
| t1: 弹出生物识别 | inactive → 再次锁定 ❌ | inactive → paused=true,跳过锁定 ✅ |
| t2: 认证完成 | 被锁定了,认证白做 | 正常完成 |
| t3: 回到 App | 又要认证一次 | unpause,流程结束 |
💡 这是整个插件最精妙的设计之一。pause/unpause 机制解决了"认证过程中触发再次锁定"的问题。
4.5 addPostFrameCallback 的作用
WidgetsBinding.instance.addPostFrameCallback((_) {
secureApplicationController.unpause();
});
为什么不直接 unpause(),而要等到下一帧?
因为 onNeedUnlock 返回后,Flutter 可能还在处理状态变化。如果立即 unpause,可能会在同一帧内触发不必要的重建。等到下一帧再 unpause,确保所有状态变化都已经处理完毕。
五、paused 状态的特殊场景
5.1 文件选择器场景
// 打开文件选择器前
controller.pause();
// 选择文件
final file = await FilePicker.platform.pickFiles();
// 选择完成后
controller.unpause();
如果不 pause,打开文件选择器会触发 inactive → paused,导致锁定。用户选完文件回来还要重新认证,体验很差。
5.2 相机/图片选择器场景
controller.pause();
final image = await ImagePicker().pickImage(source: ImageSource.camera);
controller.unpause();
5.3 第三方登录场景
controller.pause();
final result = await signInWithGoogle();
controller.unpause();
跳转到 Google 登录页面会触发 App 进入后台,需要 pause 来避免锁定。
5.4 通用模式
// 任何会导致 App 暂时离开前台的操作
controller.pause();
try {
await someExternalOperation();
} finally {
controller.unpause(); // 确保一定会 unpause
}
📌 注意:一定要在 finally 中 unpause,否则如果操作抛出异常,App 会永远处于 paused 状态,再也不会锁定。
六、build 方法中的原生遮罩移除
6.1 代码
Widget build(BuildContext context) {
if (_removeNativeOnNextFrame && widget.autoUnlockNative) {
Future.delayed(Duration(milliseconds: widget.nativeRemoveDelay))
.then((_) => SecureApplicationNative.unlock());
_removeNativeOnNextFrame = false;
}
return SecureApplicationProvider(
secureData: secureApplicationController,
child: widget.child,
);
}
6.2 nativeRemoveDelay 的作用
const SecureApplication({
this.nativeRemoveDelay = 1000, // 默认 1000ms
// ...
});
| 平台 | 需要延迟移除 | 原因 |
|---|---|---|
| iOS | ✅ | 原生模糊视图需要等 Flutter 渲染完成后再移除 |
| Android | ❌ | 没有原生遮罩 |
| OpenHarmony | ❌ | 没有原生遮罩 |
在 OpenHarmony 上,SecureApplicationNative.unlock() 会调用原生端的 unlock 方法,但那是空实现,所以这个延迟移除实际上没有效果。不过保留这个逻辑不会造成问题。
七、状态机的完整流转
7.1 正常保护流程
1. 用户调用 controller.secure()
→ secured=true, 原生端开启隐私模式
2. 用户按 Home 键
→ inactive: lock() → locked=true, 遮罩显示
→ paused: lock() → 已锁定,跳过
3. 用户切回 App
→ resumed:
→ secured=true, locked=true → 触发 onNeedUnlock
→ 用户认证成功 → authSuccess(unlock: true)
→ locked=false, 遮罩隐藏
4. 用户调用 controller.open()
→ secured=false, 原生端关闭隐私模式
7.2 暂停场景
1. secured=true, 用户正常使用
2. 用户点击"选择图片"
→ controller.pause() → paused=true
3. 系统图片选择器打开
→ inactive: paused=true → 跳过锁定 ✅
4. 用户选完图片回来
→ resumed: paused=true → 跳过认证
→ controller.unpause() → paused=false
5. 用户按 Home 键
→ inactive: paused=false, secured=true → 正常锁定
7.3 认证失败场景
1. 用户切回 App, secured=true, locked=true
2. onNeedUnlock 被调用
→ 返回 SecureApplicationAuthenticationStatus.FAILED
3. sendAuthenticationEvent(FAILED)
→ authenticationEvents 流发射 FAILED
→ onAuthenticationFailed 回调被调用
4. 遮罩仍然显示(因为没有调用 unlock)
→ 用户需要再次尝试认证
总结
本文全面解析了 secure_application 的应用生命周期状态机:
- inactive/paused:检查 paused 和 secured 标志,决定是否锁定
- resumed:触发认证流程,pause/unpause 防止认证中断
- pause 机制:文件选择器、相机等场景需要临时暂停保护
- nativeRemoveDelay:延迟移除原生遮罩,主要针对 iOS
- addPostFrameCallback:确保状态变化在下一帧生效
下一篇我们讲认证流程与 authenticationEvents 事件流——onNeedUnlock 的完整工作机制。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- Flutter AppLifecycleState
- WidgetsBindingObserver
- addPostFrameCallback
- secure_application 源码
- Flutter 生命周期指南
- FilePicker 插件
- ImagePicker 插件
- 开源鸿蒙跨平台社区


Flutter 应用生命周期状态转换图
更多推荐
所有评论(0)