前言

欢迎加入开源鸿蒙跨平台社区: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.dartmaterial.dart 都导出了 WidgetsBindingObserverAppLifecycleState。选择 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 从后台回到前台时,清理未完成的认证调用

场景:

  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 方法签名

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 源码:

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

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

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


相关资源:

请添加图片描述
Flutter MethodChannel 通信架构

Logo

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

更多推荐