Flutter三方库适配OpenHarmony【flutter_web_auth】— Dart 层源码逐行解析

前言

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

flutter_web_auth 的 Dart 层只有 55 行代码。这可能是我见过的最精简的 Flutter 插件之一。但别小看这 55 行------里面有正则校验、生命周期观察者、dangling calls 清理机制,每一行都有它存在的理由。

一、文件结构总览

1.1 完整源码

dart 复制代码
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);

  @override
  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

dart 复制代码
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

dart 复制代码
import 'package:flutter/cupertino.dart';  // 而不是 material.dart

cupertino.dart 和 material.dart 都导出了 WidgetsBindingObserver 和 AppLifecycleState。选择 cupertino 可能是因为作者更偏向 iOS 风格,但实际上两者在这里没有区别。

2.3 show 关键字的使用

dart 复制代码
import 'package:flutter/services.dart' show MethodChannel;

show 只导入 MethodChannel,避免命名冲突。这是一个好习惯------只导入需要的类。

三、FlutterWebAuth 类的静态设计

3.1 为什么全是 static

dart 复制代码
class FlutterWebAuth {
  static const MethodChannel _channel = ...;
  static RegExp _schemeRegExp = ...;
  static final _resumedObserver = ...;
  static Future<String> authenticate(...) async { ... }
  static Future<void> _cleanUpDanglingCalls() async { ... }
}

所有成员都是 static,意味着不需要实例化:

dart 复制代码
// 直接调用,不需要 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 正则表达式

dart 复制代码
static RegExp _schemeRegExp = new RegExp(r"^[a-z][a-z0-9+.-]*$");

4.2 逐字符解析

4.3 校验逻辑

dart 复制代码
if (!_schemeRegExp.hasMatch(callbackUrlScheme)) {
  throw ArgumentError.value(callbackUrlScheme, 'callbackUrlScheme', 'must be a valid URL scheme');
}

在调用原生端之前就校验,快速失败。如果 Scheme 不合法,直接抛异常,不浪费一次 MethodChannel 调用。

4.4 测试用例

dart 复制代码
// 合法
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 类定义

dart 复制代码
class _OnAppLifecycleResumeObserver extends WidgetsBindingObserver {
  final Function onResumed;

  _OnAppLifecycleResumeObserver(this.onResumed);

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      onResumed();
    }
  }
}

5.2 设计意图

这个观察者的唯一目的:当 App 从后台回到前台时,清理未完成的认证调用。

场景:

  1. 用户点击登录 → 浏览器打开
  2. 用户在浏览器中没有完成登录,直接切回 App
  3. App 回到前台 → didChangeAppLifecycleState(resumed) 触发
  4. onResumed() 被调用 → _cleanUpDanglingCalls() 执行
  5. 所有等待中的 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 方法签名

dart 复制代码
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 注册的安全措施

dart 复制代码
WidgetsBinding.instance.removeObserver(_resumedObserver); // 先移除
WidgetsBinding.instance.addObserver(_resumedObserver);     // 再添加

先 remove 再 add ,确保不会重复注册。如果用户连续调用两次 authenticate(),不会添加两个 Observer。

6.3 MethodChannel 调用

dart 复制代码
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 返回值

dart 复制代码
return await ... as String;

返回值是原生端通过 result.success(uri) 传回的字符串,格式类似:

复制代码
myapp://callback?code=abc123&state=xyz

七、_cleanUpDanglingCalls 清理机制

7.1 方法实现

dart 复制代码
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

dart 复制代码
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 源码:

  1. 静态设计:所有成员 static,不需要实例化
  2. 正则校验:RFC 3986 合规检查,快速失败
  3. Observer 机制:监听 App resumed,清理 dangling calls
  4. 安全注册:先 remove 再 add,防止重复
  5. 清理后注销:清理完成后移除 Observer,减少开销

下一篇我们分析 Android 端的 Chrome Custom Tabs 实现------理解 OpenHarmony 适配的参照物。

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


相关资源:


Flutter MethodChannel 通信架构

相关推荐
Geek_Vison9 小时前
保险APP已经用了Flutter,还能接入小程序吗?
flutter·小程序·uni-app·mpaas·小程序容器
FungLeo10 小时前
成为全栈·Flutter App 篇·go_router 与四 Tab App Shell:保留导航状态、深链和登录回跳
flutter·移动开发·成为全栈·go_router·路由设计·深链接
m0_7381858210 小时前
Flutter 鸿蒙化实战:screen_protector 适配 OpenHarmony,防截屏/防泄露
flutter·华为·harmonyos·鸿蒙
梦想的颜色15 小时前
【编程实战】AI 时代 APP 开发全栈硬核指南:技术选型 + AI 架构 + 模型落地全维度决策
python·flutter·react native·react.js·ai·桌面应用·milvus
yuanlaile1 天前
2026年全新Flutter 跨平台开发全栈学习资料整理
flutter·getx·鸿蒙 next·flutter开发鸿蒙·flutter入门实战·flutter全栈
恋猫de小郭2 天前
Genkit Dart 1.0 发布,Flutter 原生的 AI Agent 终于完整了
android·前端·flutter
第10086个小白2 天前
flutter桌面端打包方式(Inno Setup Compiler)版本
前端·flutter
陆断枫3 天前
Flutter 3.47 动画引擎盘点:Impeller 全平台,和那些"新特性"
flutter
程序员老刘3 天前
Flet 1.0 思路很好,可惜来晚了
flutter·ai编程·客户端
m0_738185823 天前
Flutter 鸿蒙化实战:qr_code_scanner_plus 适配 OpenHarmony,二维码扫描
flutter·华为·harmonyos·鸿蒙