前言

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

flutter_web_auth 的 OpenHarmony 实现中有三种明确的错误码:NO_CONTEXTLAUNCH_FAILEDCANCELED。再加上 Dart 层的 ArgumentError 和各种 BusinessError,整个错误处理体系其实不简单。这篇把所有可能出错的地方都梳理一遍。

一、错误码全景

1.1 错误码清单

在这里插入图片描述

1.2 错误发生的时间线

authenticate() 调用
    │
    ├── Dart 层:Scheme 校验 → ArgumentError
    │
    ├── 原生层:Context 检查 → NO_CONTEXT
    │
    ├── 原生层:openLink + startAbility → LAUNCH_FAILED
    │
    ├── 等待回调...
    │
    └── 用户取消 → CANCELED

二、NO_CONTEXT 错误

2.1 触发条件

if (!context) {
  console.error(TAG, 'UIAbilityContext is null, cannot open browser');
  FlutterWebAuthPlugin.callbacks.delete(callbackUrlScheme);
  result.error("NO_CONTEXT", "Unable to get UIAbilityContext. AbilityAware may not be working.", null);
  return;
}

2.2 可能的原因

原因 说明 解决方案
AbilityAware 未生效 框架没有调用 onAttachedToAbility 检查插件注册
调用时机过早 onAttachedToAbility 还没执行 延迟调用
插件已 detach onDetachedFromAbility 已执行 检查生命周期

2.3 防御逻辑

private authenticate(url: string, callbackUrlScheme: string, result: MethodResult): void {
  // 1. 先注册回调
  FlutterWebAuthPlugin.callbacks.set(callbackUrlScheme, result);

  // 2. 获取 context
  let context: common.UIAbilityContext | null = null;
  if (this.ability) {
    context = this.ability.context;
  } else {
    console.error(TAG, 'ability is null!');
  }

  // 3. 检查 context
  if (!context) {
    FlutterWebAuthPlugin.callbacks.delete(callbackUrlScheme);  // 清理已注册的回调
    result.error("NO_CONTEXT", "...", null);
    return;
  }
  // ...
}

📌 注意清理逻辑:如果 context 获取失败,必须从 callbacks Map 中删除已注册的回调,否则会成为僵尸条目。

2.4 Dart 层处理

try {
  final result = await FlutterWebAuth.authenticate(...);
} on PlatformException catch (e) {
  if (e.code == 'NO_CONTEXT') {
    // 插件初始化问题,建议重启应用
    showDialog(
      context: context,
      builder: (_) => AlertDialog(
        title: Text('初始化错误'),
        content: Text('请重启应用后重试'),
      ),
    );
  }
}

三、LAUNCH_FAILED 错误

3.1 触发条件

// openLink 失败后的 startAbility 也失败
context.startAbility(want)
  .catch((err: BusinessError) => {
    FlutterWebAuthPlugin.callbacks.delete(callbackUrlScheme);
    result.error("LAUNCH_FAILED", `Failed to open URL: ${err.code} - ${err.message}`, null);
  });

3.2 触发流程

openLink(url)
    │
    ├── 成功 → 等待回调
    │
    └── 失败 → startAbilityFallback(url)
                    │
                    ├── 成功 → 等待回调
                    │
                    └── 失败 → LAUNCH_FAILED

只有 openLink startAbility 都失败时才会触发 LAUNCH_FAILED。

3.3 可能的原因

原因 BusinessError 码 解决方案
没有可用的浏览器 匹配失败 安装浏览器应用
URL 格式无效 参数错误 检查 URL 格式
INTERNET 权限未声明 权限错误 在 module.json5 中添加
系统限制 各种 检查设备设置

3.4 BusinessError 常见错误码

错误码 含义 场景
16000001 指定的 Ability 不存在 没有浏览器应用
16000002 参数不正确 URL 格式错误
16000004 后台启动限制 App 在后台时尝试启动
16000011 context 不存在 Ability 已销毁

3.5 Dart 层处理

on PlatformException catch (e) {
  if (e.code == 'LAUNCH_FAILED') {
    showSnackBar('无法打开浏览器: ${e.message}');
  }
}

四、CANCELED 错误

4.1 触发条件

private cleanUpDanglingCalls(result: MethodResult): void {
  FlutterWebAuthPlugin.callbacks.forEach((danglingResult: MethodResult) => {
    danglingResult.error("CANCELED", "User canceled login", null);
  });
  FlutterWebAuthPlugin.callbacks.clear();
  result.success(null);
}

4.2 触发场景

场景 说明
用户在浏览器中按返回键 切回 App
用户通过任务管理器切回 不经过浏览器
用户点击 Home 键后打开 App 不是通过深度链接

4.3 CANCELED 不是错误

on PlatformException catch (e) {
  if (e.code == 'CANCELED') {
    // 这是正常行为,不需要显示错误提示
    // 用户只是改主意了
    return;
  }
}

💡 CANCELED 是最常见的"错误",但它其实是正常行为。用户随时可以取消登录,App 应该优雅地处理这种情况。

五、Dart 层的 ArgumentError

5.1 触发条件

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

5.2 常见的不合法 Scheme

Scheme 问题 修正
My_App 大写 + 下划线 my-app
1app 数字开头 app1
my app 包含空格 my-app
`` 空字符串 填写有效 Scheme

5.3 这个错误在原生层之前

authenticate() 调用
    ↓
Dart 层:_schemeRegExp 校验 → 不合法 → 抛出 ArgumentError
    ↓
不会到达原生层

ArgumentError 是 Dart 层的同步异常,不经过 MethodChannel。

六、MissingPluginException

6.1 触发条件

Dart 层调用 MethodChannel.invokeMethod('authenticate')
    ↓
原生层没有注册 Handler
    ↓
MissingPluginException: No implementation found for method authenticate on channel flutter_web_auth

6.2 可能的原因

原因 解决方案
pubspec.yaml 缺少 ohos 平台声明 添加 ohos 配置
pluginClass 名称不匹配 检查名称一致性
插件未被 GeneratedPluginRegistrant 注册 重新运行 flutter pub get
通道名称不一致 检查两端的通道名称

6.3 排查步骤

  1. 检查 pubspec.yaml 中是否有 ohos 平台声明
  2. 检查 pluginClass 是否与 ArkTS 类名一致
  3. 检查 GeneratedPluginRegistrant 是否包含插件
  4. 检查通道名称是否两端一致
# pubspec.yaml
flutter:
  plugin:
    platforms:
      ohos:
        package: com.linusu.flutter_web_auth
        pluginClass: FlutterWebAuthPlugin  # 必须与 ArkTS 类名一致

七、错误处理的完整模板

7.1 Dart 层最佳实践

Future<String?> performAuth() async {
  try {
    final result = await FlutterWebAuth.authenticate(
      url: authUrl,
      callbackUrlScheme: callbackScheme,
    );
    
    // 验证 state 参数
    final returnedState = Uri.parse(result).queryParameters['state'];
    if (returnedState != expectedState) {
      throw Exception('State mismatch');
    }
    
    return Uri.parse(result).queryParameters['code'];
    
  } on ArgumentError catch (e) {
    // Scheme 不合法 — 代码错误
    debugPrint('Scheme 不合法: $e');
    rethrow;
    
  } on PlatformException catch (e) {
    switch (e.code) {
      case 'CANCELED':
        // 用户取消 — 正常行为
        return null;
      case 'NO_CONTEXT':
        // 插件初始化问题
        showError('请重启应用后重试');
        return null;
      case 'LAUNCH_FAILED':
        // 浏览器打开失败
        showError('无法打开浏览器: ${e.message}');
        return null;
      default:
        showError('登录失败: ${e.message}');
        return null;
    }
  } on MissingPluginException {
    // 插件未注册
    showError('插件配置错误,请联系开发者');
    return null;
  }
}

7.2 错误处理决策表

错误 用户提示 重试 日志级别
ArgumentError 不提示(开发错误) error
NO_CONTEXT “请重启应用” error
LAUNCH_FAILED “无法打开浏览器” warn
CANCELED 不提示 info
MissingPluginException “请联系开发者” error

7.3 重试策略

Future<String?> authWithRetry({int maxRetries = 3}) async {
  for (int i = 0; i < maxRetries; i++) {
    final code = await performAuth();
    if (code != null) return code;
    
    // 如果是 CANCELED,不自动重试
    // 让用户决定是否重新点击登录
    break;
  }
  return null;
}

八、原生层的防御性编程总结

8.1 防御点清单

位置 防御内容 处理方式
authenticate 入口 ability 为 null 日志 + 继续检查 context
authenticate 入口 context 为 null 清理 callback + error
openLink 同步异常 try-catch → fallback
openLink 异步失败 Promise.catch → fallback
startAbility 异步失败 清理 callback + error
onNewWant uri 为空 静默返回
onNewWant 无 😕/ 静默返回
onNewWant 无匹配 callback 静默返回

8.2 静默失败 vs 显式错误

场景 策略 原因
authenticate 失败 显式错误 调用者需要知道
onNewWant 不匹配 静默忽略 可能不是认证回调
cleanUpDanglingCalls 显式 CANCELED 调用者需要知道

📌 原则:对调用者发起的操作返回显式错误,对系统触发的事件静默处理

总结

本文全面梳理了 flutter_web_auth 的错误处理体系:

  1. 五种错误:ArgumentError、NO_CONTEXT、LAUNCH_FAILED、CANCELED、MissingPluginException
  2. CANCELED 不是错误:用户取消是正常行为
  3. 双重降级:openLink → startAbility → LAUNCH_FAILED
  4. 清理逻辑:错误发生时必须清理 callbacks Map
  5. Dart 层模板:switch-case 分类处理不同错误码

下一篇我们讲示例应用开发——完整的 OAuth2 实战接入。

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


相关资源:

Logo

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

更多推荐