前言

欢迎加入开源鸿蒙跨平台社区: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 签名

openLink(link: string, options?: OpenLinkOptions): Promise<void>

1.2 基本用法

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.3 openLink 的行为

在这里插入图片描述

1.4 与 Android startActivity 的对比

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

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

二、openLink 的异步特性与 Promise 错误处理

2.1 flutter_web_auth 中的实现

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 为什么需要两层错误处理

// 场景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 实现

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 参数

let want: Want = {
  action: 'ohos.want.action.viewData',  // 查看数据
  uri: url,                              // 要打开的 URL
};
字段 作用
action ohos.want.action.viewData 告诉系统"我要查看这个 URL"
uri https://auth.example.com/… 要打开的认证页面

3.3 startAbility vs openLink

维度 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()(同上)

四、openLink 成功后的等待

4.1 一个容易忽略的细节

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 实现

// Android
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)

5.2 OpenHarmony 实现

// 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
浏览器选择 系统选择 系统选择 系统选择
推荐度 ⚠️

六、openLink 可能失败的场景

6.1 常见失败原因

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

6.2 网络权限

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

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

6.3 URL 格式要求

// ✅ 合法的 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 层的用户体验优化
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 的。

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


相关资源:

Logo

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

更多推荐