Flutter三方库适配OpenHarmony【flutter_web_auth】— openLink API 与浏览器启动策略

前言

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

打开浏览器这件事,在 Android 上一行 startActivity(intent) 就搞定了。在 OpenHarmony 上稍微复杂一点------首选方案是 context.openLink(url),如果失败了还有 context.startAbility(want) 作为降级。flutter_web_auth 实现了一个 try-catch-fallback 模式来确保浏览器一定能打开。

一、context.openLink(url) API 详解

1.1 API 签名

typescript 复制代码
openLink(link: string, options?: OpenLinkOptions): Promise<void>

1.2 基本用法

typescript 复制代码
context.openLink(url)
  .then(() => {
    console.info(TAG, 'openLink succeeded, browser should be open');
  })
  .catch((err: BusinessError) => {
    console.error(TAG, `openLink failed: ${err.code} ${err.message}`);
  });

1.4 与 Android startActivity 的对比

维度 Android startActivity OpenHarmony openLink
同步/异步 同步 异步
错误反馈 抛异常 Promise reject
参数 Intent 对象 URL 字符串
灵活性 高(可配置 Intent) 中(只接受 URL)

📌 openLink 是异步的 ,这意味着调用后不能立即假设浏览器已经打开。需要通过 .then() 确认成功,通过 .catch() 处理失败。

2.1 flutter_web_auth 中的实现

typescript 复制代码
private authenticate(url: string, callbackUrlScheme: string, result: MethodResult): void {
  FlutterWebAuthPlugin.callbacks.set(callbackUrlScheme, result);

  let context: common.UIAbilityContext | null = null;
  if (this.ability) {
    context = this.ability.context;
  }

  if (!context) {
    FlutterWebAuthPlugin.callbacks.delete(callbackUrlScheme);
    result.error("NO_CONTEXT", "Unable to get UIAbilityContext.", null);
    return;
  }

  try {
    context.openLink(url)
      .then(() => {
        console.info(TAG, 'openLink succeeded');
      })
      .catch((err: BusinessError) => {
        console.error(TAG, `openLink failed: ${err.code} ${err.message}`);
        this.startAbilityFallback(context!, url, callbackUrlScheme, result);
      });
  } catch (e) {
    this.startAbilityFallback(context, url, callbackUrlScheme, result);
  }
}

2.2 双重错误处理

复制代码
try {
  context.openLink(url)        ← 可能同步抛异常
    .then(...)                  ← 成功
    .catch(err => ...)          ← 异步失败
} catch (e) {                   ← 同步异常
  fallback(...)
}
错误类型 捕获方式 示例
同步异常 try-catch openLink 方法不存在(低版本 API)
异步失败 Promise.catch URL 格式错误、无可用浏览器

2.3 为什么需要两层错误处理

typescript 复制代码
// 场景1:openLink 方法本身抛异常(同步)
// 可能在低版本 API 上 openLink 不存在
try {
  context.openLink(url);  // TypeError: openLink is not a function
} catch (e) {
  // 被 try-catch 捕获
}

// 场景2:openLink 调用成功但执行失败(异步)
context.openLink("invalid-url")
  .catch((err) => {
    // 被 Promise.catch 捕获
  });

💡 这种 try-catch + Promise.catch 的双重保护模式,在 OpenHarmony 开发中很常见。因为有些 API 在不同版本上的行为不一致。

三、startAbility 降级方案

3.1 实现

typescript 复制代码
private startAbilityFallback(
  context: common.UIAbilityContext,
  url: string,
  callbackUrlScheme: string,
  result: MethodResult
): void {
  let want: Want = {
    action: 'ohos.want.action.viewData',
    uri: url,
  };

  context.startAbility(want)
    .then(() => {
      console.info(TAG, 'startAbility fallback succeeded');
    })
    .catch((err: BusinessError) => {
      console.error(TAG, `startAbility fallback also failed: ${err.code} ${err.message}`);
      FlutterWebAuthPlugin.callbacks.delete(callbackUrlScheme);
      result.error("LAUNCH_FAILED", `Failed to open URL: ${err.code} - ${err.message}`, null);
    });
}

3.2 Want 参数

typescript 复制代码
let want: Want = {
  action: 'ohos.want.action.viewData',  // 查看数据
  uri: url,                              // 要打开的 URL
};
字段 作用
action ohos.want.action.viewData 告诉系统"我要查看这个 URL"
uri https://auth.example.com/... 要打开的认证页面
维度 openLink startAbility
参数 URL 字符串 Want 对象
灵活性
推荐度 ✅ 推荐 ⚠️ 降级方案
API 版本 较新 较早

3.4 降级策略流程图

复制代码
authenticate(url, scheme)
    │
    ├── context == null?
    │       └── YES → error("NO_CONTEXT") → 结束
    │
    └── context != null
            │
            ├── try openLink(url)
            │       │
            │       ├── .then() → 成功,等待回调
            │       │
            │       └── .catch() → 失败
            │               │
            │               └── startAbilityFallback()
            │                       │
            │                       ├── .then() → 成功,等待回调
            │                       │
            │                       └── .catch() → 彻底失败
            │                               │
            │                               └── error("LAUNCH_FAILED")
            │
            └── catch (同步异常)
                    │
                    └── startAbilityFallback()(同上)

4.1 一个容易忽略的细节

typescript 复制代码
context.openLink(url)
  .then(() => {
    console.info(TAG, 'openLink succeeded, browser should be open');
    // 注意:这里没有调用 result.success()
    // 因为认证还没完成,需要等待深度链接回调
  });

openLink 成功只意味着浏览器打开了,不意味着认证完成了。认证结果要等到用户在浏览器中完成操作,浏览器重定向到回调 URL,系统通过深度链接唤起 App 后才能拿到。

4.2 等待期间的状态

复制代码
openLink 成功
    ↓
浏览器打开认证页面
    ↓
App 进入后台(或保持前台但失去焦点)
    ↓
callbacks Map 中有一个 pending 的 MethodResult
    ↓
等待 onNewWant 被调用...

4.3 等待期间可能发生的事

事件 处理
用户完成认证 onNewWant → success
用户取消(切回 App) resumed → cleanUpDanglingCalls → error("CANCELED")
App 被系统杀掉 callbacks 丢失,认证失败
用户很久不操作 一直等待,直到用户回来

五、与 Android startActivity(Intent.ACTION_VIEW) 的对比

5.1 Android 实现

kotlin 复制代码
// Android
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)

5.2 OpenHarmony 实现

typescript 复制代码
// OpenHarmony - 方案1
context.openLink(url);

// OpenHarmony - 方案2(降级)
context.startAbility({
  action: 'ohos.want.action.viewData',
  uri: url,
});

5.3 对比表

维度 Android OpenHarmony (openLink) OpenHarmony (startAbility)
参数类型 Intent String Want
同步/异步 同步 异步 异步
错误处理 try-catch Promise.catch Promise.catch
浏览器选择 系统选择 系统选择 系统选择
推荐度 ⚠️

6.1 常见失败原因

原因 错误码 解决方案
URL 格式无效 参数错误 校验 URL 格式
没有可用的浏览器 匹配失败 降级到 startAbility
网络权限未声明 权限错误 添加 INTERNET 权限
API 版本不支持 方法不存在 降级到 startAbility

6.2 网络权限

json5 复制代码
// 宿主应用的 module.json5
{
  "module": {
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" }
    ]
  }
}

📌 INTERNET 权限必须在宿主应用中声明,不是在插件的 module.json5 中。插件的 module.json5 只声明模块元数据,不声明权限。

6.3 URL 格式要求

typescript 复制代码
// ✅ 合法的 URL
context.openLink("https://accounts.google.com/o/oauth2/v2/auth?...");

// ❌ 不合法的 URL
context.openLink("not a url");  // 可能失败
context.openLink("");            // 空字符串

七、浏览器启动的用户体验

7.1 启动流程的用户感知

复制代码
1. 用户点击"登录"按钮
2. 短暂的加载动画(openLink 异步)
3. 系统浏览器打开,显示认证页面
4. 用户在浏览器中操作
5. 认证完成,自动跳回 App

7.2 优化建议

优化项 做法 效果
加载提示 调用 authenticate 后显示 loading 用户知道在等待
超时处理 设置合理的超时时间 避免无限等待
错误提示 捕获 PlatformException 告诉用户发生了什么
dart 复制代码
// Dart 层的用户体验优化
setState(() => _isLoading = true);
try {
  final result = await FlutterWebAuth.authenticate(
    url: authUrl,
    callbackUrlScheme: scheme,
  );
  // 处理结果
} on PlatformException catch (e) {
  if (e.code == 'CANCELED') {
    // 用户取消,不需要提示
  } else {
    // 显示错误提示
    showSnackBar('登录失败: ${e.message}');
  }
} finally {
  setState(() => _isLoading = false);
}

总结

本文详细分析了 flutter_web_auth 的浏览器启动策略:

  1. openLink:推荐方案,异步打开系统浏览器
  2. startAbility:降级方案,通过 Want 打开浏览器
  3. 双重错误处理:try-catch + Promise.catch
  4. 等待机制:openLink 成功后等待深度链接回调
  5. INTERNET 权限:必须在宿主应用中声明

下一篇我们讲静态回调 Map------认证结果是怎么从 onNewWant 传回 Dart 的。

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


相关资源:

相关推荐
LawrenceLan2 小时前
31.Flutter 零基础入门(三十一):Stack 与 Positioned —— 悬浮、角标与覆盖布局
开发语言·前端·flutter·dart
lili-felicity2 小时前
基础入门 Flutter for OpenHarmony:第三方库实战 cryptography_flutter 加密解密详解
flutter
lqj_本人2 小时前
Flutter三方库适配OpenHarmony【apple_product_name】构建设备信息展示页面
flutter
阿林来了2 小时前
Flutter三方库适配OpenHarmony【flutter_web_auth】— 深度链接(Deep Link)机制全解析
flutter
松叶似针2 小时前
Flutter三方库适配OpenHarmony【doc_text】— FlutterPlugin 接口实现与 MethodChannel 注册
flutter·harmonyos
lqj_本人2 小时前
Flutter三方库适配OpenHarmony【apple_product_name】插件架构设计解析
flutter
wangyang62752 小时前
Xcode 26 真机运行崩溃 EXC_BAD_ACCESS map_images_nolock 完美解决方案
flutter·ios
2601_949593652 小时前
Flutter for Harmony 跨平台开发实战:超形状与超椭圆——参数方程的形态边界
flutter
Swift社区3 小时前
Flutter 中如何优雅地处理复杂表单
前端·flutter·前端框架