Flutter三方库适配OpenHarmony【flutter_web_auth】— Dart 层源码逐行解析
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.netflutter_web_auth 的 Dart 层只有55 行代码。这可能是我见过的最精简的 Flutter 插件之一。但别小看这 55 行——里面有正则校验、生命周期观察者、dangling calls 清理机制,每一行都有它存在的理由。@override静态设计:所有成员 static
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
flutter_web_auth 的 Dart 层只有 55 行代码。这可能是我见过的最精简的 Flutter 插件之一。但别小看这 55 行——里面有正则校验、生命周期观察者、dangling calls 清理机制,每一行都有它存在的理由。
一、文件结构总览
1.1 完整源码
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart' show MethodChannel;
class _OnAppLifecycleResumeObserver extends WidgetsBindingObserver {
final Function onResumed;
_OnAppLifecycleResumeObserver(this.onResumed);
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
onResumed();
}
}
}
class FlutterWebAuth {
static const MethodChannel _channel = const MethodChannel('flutter_web_auth');
static RegExp _schemeRegExp = new RegExp(r"^[a-z][a-z0-9+.-]*$");
static final _OnAppLifecycleResumeObserver _resumedObserver = _OnAppLifecycleResumeObserver(() {
_cleanUpDanglingCalls();
});
static Future<String> authenticate({required String url, required String callbackUrlScheme, bool? preferEphemeral}) async {
if (!_schemeRegExp.hasMatch(callbackUrlScheme)) {
throw ArgumentError.value(callbackUrlScheme, 'callbackUrlScheme', 'must be a valid URL scheme');
}
WidgetsBinding.instance.removeObserver(_resumedObserver);
WidgetsBinding.instance.addObserver(_resumedObserver);
return await _channel.invokeMethod('authenticate', <String, dynamic>{
'url': url,
'callbackUrlScheme': callbackUrlScheme,
'preferEphemeral': preferEphemeral ?? false,
}) as String;
}
static Future<void> _cleanUpDanglingCalls() async {
await _channel.invokeMethod('cleanUpDanglingCalls');
WidgetsBinding.instance.removeObserver(_resumedObserver);
}
}
1.2 代码量统计
| 部分 | 行数 | 占比 |
|---|---|---|
| import 语句 | 4 | 7% |
| _OnAppLifecycleResumeObserver | 14 | 25% |
| FlutterWebAuth 类 | 37 | 68% |
| 总计 | 55 | 100% |
二、import 语句分析
2.1 四个 import
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart' show MethodChannel;
| import | 用途 | 提供的类 |
|---|---|---|
dart:async |
Future 异步支持 | Future |
flutter/cupertino.dart |
Widget 框架(含 WidgetsBindingObserver) | WidgetsBinding, AppLifecycleState |
flutter/services.dart |
平台通道 | MethodChannel |
2.2 为什么 import cupertino 而不是 material
import 'package:flutter/cupertino.dart'; // 而不是 material.dart
cupertino.dart 和 material.dart 都导出了 WidgetsBindingObserver 和 AppLifecycleState。选择 cupertino 可能是因为作者更偏向 iOS 风格,但实际上两者在这里没有区别。
2.3 show 关键字的使用
import 'package:flutter/services.dart' show MethodChannel;
show 只导入 MethodChannel,避免命名冲突。这是一个好习惯——只导入需要的类。
三、FlutterWebAuth 类的静态设计
3.1 为什么全是 static
class FlutterWebAuth {
static const MethodChannel _channel = ...;
static RegExp _schemeRegExp = ...;
static final _resumedObserver = ...;
static Future<String> authenticate(...) async { ... }
static Future<void> _cleanUpDanglingCalls() async { ... }
}
所有成员都是 static,意味着不需要实例化:
// 直接调用,不需要 new
final result = await FlutterWebAuth.authenticate(...);
| 设计选择 | 原因 |
|---|---|
| 不需要实例化 | 认证是一次性操作,不需要维护状态 |
| MethodChannel 是 const | 通道名称固定,全局唯一 |
| Observer 是 final | 只需要一个观察者实例 |
💡 与 secure_application 的对比:secure_application 使用了 StatefulWidget + Controller 的实例化设计,因为它需要维护 locked/secured 等持续状态。flutter_web_auth 是"调一次就完事"的模式,static 更合适。
四、_schemeRegExp 正则校验
4.1 正则表达式
static RegExp _schemeRegExp = new RegExp(r"^[a-z][a-z0-9+.-]*$");
4.2 逐字符解析

4.3 校验逻辑
if (!_schemeRegExp.hasMatch(callbackUrlScheme)) {
throw ArgumentError.value(callbackUrlScheme, 'callbackUrlScheme', 'must be a valid URL scheme');
}
在调用原生端之前就校验,快速失败。如果 Scheme 不合法,直接抛异常,不浪费一次 MethodChannel 调用。
4.4 测试用例
// 合法
assert(_schemeRegExp.hasMatch("myapp")); // true
assert(_schemeRegExp.hasMatch("com.example.app")); // true
assert(_schemeRegExp.hasMatch("my-app")); // true
assert(_schemeRegExp.hasMatch("my+app")); // true
// 不合法
assert(!_schemeRegExp.hasMatch("MyApp")); // false(大写)
assert(!_schemeRegExp.hasMatch("my_app")); // false(下划线)
assert(!_schemeRegExp.hasMatch("1app")); // false(数字开头)
assert(!_schemeRegExp.hasMatch("")); // false(空字符串)
五、_OnAppLifecycleResumeObserver 观察者
5.1 类定义
class _OnAppLifecycleResumeObserver extends WidgetsBindingObserver {
final Function onResumed;
_OnAppLifecycleResumeObserver(this.onResumed);
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
onResumed();
}
}
}
5.2 设计意图
这个观察者的唯一目的:当 App 从后台回到前台时,清理未完成的认证调用。
场景:
- 用户点击登录 → 浏览器打开
- 用户在浏览器中没有完成登录,直接切回 App
- App 回到前台 →
didChangeAppLifecycleState(resumed)触发 onResumed()被调用 →_cleanUpDanglingCalls()执行- 所有等待中的
authenticate()调用收到CANCELED错误
5.3 为什么用私有类
类名以 _ 开头,是 Dart 的私有约定。这个类只在 flutter_web_auth.dart 内部使用,不暴露给外部。
5.4 与 secure_application 的 WidgetsBindingObserver 对比
| 维度 | flutter_web_auth | secure_application |
|---|---|---|
| 监听状态 | 只关心 resumed | 关心 resumed/inactive/paused |
| 目的 | 清理 dangling calls | 锁定/解锁 |
| 注册方式 | 动态注册/注销 | initState 中注册 |
| 生命周期 | 认证期间 | Widget 生命周期 |
六、authenticate 方法详解
6.1 方法签名
static Future<String> authenticate({
required String url,
required String callbackUrlScheme,
bool? preferEphemeral,
}) async { ... }
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| url | String | ✅ | 认证页面的 URL |
| callbackUrlScheme | String | ✅ | 回调 URL 的 Scheme |
| preferEphemeral | bool? | ❌ | 是否使用隐私浏览模式 |
6.2 Observer 注册的安全措施
WidgetsBinding.instance.removeObserver(_resumedObserver); // 先移除
WidgetsBinding.instance.addObserver(_resumedObserver); // 再添加
先 remove 再 add,确保不会重复注册。如果用户连续调用两次 authenticate(),不会添加两个 Observer。
6.3 MethodChannel 调用
return await _channel.invokeMethod('authenticate', <String, dynamic>{
'url': url,
'callbackUrlScheme': callbackUrlScheme,
'preferEphemeral': preferEphemeral ?? false,
}) as String;
| 参数 | 传递给原生端 | 默认值 |
|---|---|---|
| url | ✅ | 无 |
| callbackUrlScheme | ✅ | 无 |
| preferEphemeral | ✅ | false |
📌
preferEphemeral ?? false:如果调用者没传这个参数,默认为false。在 OpenHarmony 上这个参数目前没有效果,因为 openLink 不支持隐私浏览模式。
6.4 返回值
return await ... as String;
返回值是原生端通过 result.success(uri) 传回的字符串,格式类似:
myapp://callback?code=abc123&state=xyz
七、_cleanUpDanglingCalls 清理机制
7.1 方法实现
static Future<void> _cleanUpDanglingCalls() async {
await _channel.invokeMethod('cleanUpDanglingCalls');
WidgetsBinding.instance.removeObserver(_resumedObserver);
}
7.2 执行流程
App 回到前台
↓
_resumedObserver.didChangeAppLifecycleState(resumed)
↓
_cleanUpDanglingCalls()
↓
MethodChannel → 原生端 cleanUpDanglingCalls
↓
原生端遍历 callbacks Map,对每个 pending result 返回 CANCELED 错误
↓
清空 callbacks Map
↓
移除 Observer(不再监听生命周期)
7.3 为什么清理后要移除 Observer
WidgetsBinding.instance.removeObserver(_resumedObserver);
清理完成后,没有 pending 的认证调用了,不需要再监听生命周期变化。下次调用 authenticate() 时会重新注册。
这样做的好处:
- 减少不必要的生命周期回调
- 避免在正常使用 App 时触发清理逻辑
八、完整调用时序
8.1 正常流程
1. Dart: authenticate(url, scheme)
2. Dart: 校验 scheme 合法性 ✓
3. Dart: 注册 Observer
4. Dart → Native: invokeMethod('authenticate', {url, scheme})
5. Native: callbacks[scheme] = result
6. Native: openLink(url) → 浏览器打开
7. 用户完成认证 → 浏览器重定向到 scheme://...
8. 系统唤起 App → onNewWant(want)
9. Native: callbacks[scheme].success(uri)
10. Dart: authenticate() 返回 uri
8.2 用户取消流程
1-6: 同上
7. 用户没有完成认证,直接切回 App
8. App resumed → Observer 触发
9. Dart → Native: invokeMethod('cleanUpDanglingCalls')
10. Native: 遍历 callbacks,返回 CANCELED 错误
11. Dart: authenticate() 抛出 PlatformException(CANCELED)
12. Dart: 移除 Observer
总结
本文逐行解析了 flutter_web_auth 的 55 行 Dart 源码:
- 静态设计:所有成员 static,不需要实例化
- 正则校验:RFC 3986 合规检查,快速失败
- Observer 机制:监听 App resumed,清理 dangling calls
- 安全注册:先 remove 再 add,防止重复
- 清理后注销:清理完成后移除 Observer,减少开销
下一篇我们分析 Android 端的 Chrome Custom Tabs 实现——理解 OpenHarmony 适配的参照物。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- flutter_web_auth 源码
- WidgetsBindingObserver 文档
- MethodChannel 文档
- RFC 3986 URI 规范
- Dart RegExp 文档
- AppLifecycleState
- ArgumentError 文档
- 开源鸿蒙跨平台社区

Flutter MethodChannel 通信架构
更多推荐

所有评论(0)