给鸿蒙 App 增加打开外部网页能力 —— flutter_web_browser 的鸿蒙使用指南

本文配套仓库:https://atomgit.com/oh-flutter/flutter_web_browser(TAG:0.17.3-ohos-1.0.0-beta.1,分支:feat/ohos_flutter_web_browser_0.17.3)。本文讲怎么用:引入依赖、调接口、跑通打开网页流程;适配是怎么一步步做出来的,见姊妹篇《flutter_web_browser 的鸿蒙适配教程》

一、最终运行效果

先看跑起来是什么样子。下面是示例工程的实测截图,主验证环境为 HUAWEI ADA-AL10U 真机(HarmonyOS 6.1.0.135 / API 24),Flutter 3.41.10-ohos-1.0.1。

应用启动后主界面顶部显示 “Running on ohos”,下方依次是预热按钮、两个打开网页按钮与 OpenHarmony 演示段:

在这里插入图片描述

图一:example 启动后的主界面

点击 “Open Flutter website”,系统默认浏览器被拉起,地址栏载入目标网址:

在这里插入图片描述

图二:点击按钮后系统默认浏览器被拉起

页面随后重定向到 flutter.dev 并完整渲染:

在这里插入图片描述

图三:flutter.dev 在系统浏览器中渲染完成

点击 “Warmup browser website” 验证预热接口,应用前台无任何变化、无崩溃——鸿蒙拉起系统浏览器无需预热(见 5.4):

在这里插入图片描述

图四:点击预热按钮后应用前台无变化

在鸿蒙上打开一个网页就是一次 openWebPage 调用:不需要写平台分支,不需要声明权限,拉起、加载、重定向全由系统浏览器完成。接入成本如何?往下看。

二、flutter_web_browser 是什么

flutter_web_browser 是 pub.dev 上的一个打开网页的 Flutter 插件(作者 Victor Bonnet,MIT 协议,2018 年首发),职责单一:在应用内浏览器中打开外部网页,避免用户直接跳出 App。Android 上基于 Chrome Custom Tabs 提供轻量级内嵌标签页,iOS 上基于 SFSafariViewController,调用方只需一行 FlutterWebBrowser.openWebPage(url: ...)

上游支持 Android 与 iOS,适配鸿蒙后新增 OpenHarmony / HarmonyOS 支持:插件在鸿蒙上通过系统隐式 Want(ohos.want.action.viewData)拉起系统默认浏览器打开网页。本仓库已发布适配版本:oh-flutter/flutter_web_browser(TAG 0.17.3-ohos-1.0.0-beta.1),接口签名与各平台完全一致。

四个接口一览:

接口描述返回值鸿蒙支持
FlutterWebBrowser.openWebPage打开网页Future<void>
FlutterWebBrowser.warmup预热浏览器服务Future<bool>是(恒返回 true)
FlutterWebBrowser.close关闭当前打开的浏览器Future<void>否(仅 iOS)
FlutterWebBrowser.events监听浏览器事件Stream<BrowserEvent>否(仅 iOS,返回空流)

典型的使用场景:用户协议与隐私政策、帮助中心、活动落地页、站外文章跳转——业务里出现"从 App 里打开一个网页",这个插件就能覆盖。

三、环境准备

环境搭建的完整步骤(ohos 版 SDK、DevEco Studio、签名配置)官方指南已经写得很细,直接照做即可:

Flutter OHOS 开发环境搭建指南

本文所有实测均在以下环境完成:

版本
Flutter(ohos 版)3.41.10-ohos-1.0.1
编译 SDK6.1.0(23)
设备HUAWEI ADA-AL10U 真机(nova 12 Ultra 星耀版,HarmonyOS 6.1.0.135 / API 24)

两点提醒:

  1. 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
  2. 运行示例需要一台已签名配置好的鸿蒙真机。

四、引入依赖

进入工程目录,在 pubspec.yaml 中添加 git 依赖:

dependencies:
  flutter_web_browser:
    git:
      url: https://atomgit.com/oh-flutter/flutter_web_browser.git
      # ref: 根据下方表格选择不同框架适配的 TAG 版本
      ref: 0.17.3-ohos-1.0.0-beta.1

执行命令拉取依赖:

flutter pub get

TAG 命名规则:原库版本-ohos-版本号-beta.x

Flutter 框架版本TAG 名称分支名
3.41.100.17.3-ohos-1.0.0-beta.1feat/ohos_flutter_web_browser_0.17.3

该 TAG 已在 3.41.10-ohos-1.0.1 搭配编译 SDK 6.1.0(23) 与 HUAWEI ADA-AL10U 真机(HarmonyOS 6.1.0.135 / API 24)实测通过,打开网页全链路已验证。不同 TAG 之间的变更详见仓库中的 CHANGELOG.OpenHarmony.md。

五、代码接入

5.1 导入库

import 'package:flutter_web_browser/flutter_web_browser.dart';

5.2 打开一个网页

await FlutterWebBrowser.openWebPage(url: 'https://flutter.dev');

url 是唯一必填参数,String 类型,原样透传给系统浏览器。在鸿蒙设备上运行,系统默认浏览器被拉起并载入网页,整个过程一行平台判断都不需要写。

返回值语义:openWebPage 返回 Future<void>,浏览器拉起成功时正常完成;失败时抛出 PlatformException,错误码为 invalid_url(url 为空)或 no_activity(拉起失败),与上游 Android 的错误码体系一致。

5.3 传定制参数

接口签名与上游一致,两组定制参数分别服务 Android 与 iOS:

FlutterWebBrowser.openWebPage(
  url: 'https://flutter.dev',
  customTabsOptions: const CustomTabsOptions(
    colorScheme: CustomTabsColorScheme.dark,
    showTitle: true,
    urlBarHidingEnabled: true,
  ),
  safariVCOptions: const SafariViewControllerOptions(
    preferredBarTintColor: Colors.black,
    preferredControlTintColor: Colors.white,
  ),
);

参数在各平台的生效情况:

参数组AndroidiOS鸿蒙
customTabsOptions生效(Custom Tabs 外观)忽略静默忽略
safariVCOptions忽略生效(SFSafariViewController 外观)静默忽略

鸿蒙上定制参数被静默忽略属于设计内行为:系统浏览器的外观不可由调用方定制,仅 url 生效。跨平台代码不需要为鸿蒙删掉参数——传了不报错、不崩溃,同一段调用代码原样运行(机理见第七章)。

5.4 预热浏览器服务

await FlutterWebBrowser.warmup();

warmup 用于在真正打开网页前预热浏览器服务,返回 Future<bool>。鸿蒙上拉起的是系统浏览器,无需预热,恒返回 true。既有"先 warmup 再 openWebPage"的代码模式在鸿蒙上原样成立,不需要任何条件编译。

5.5 关闭浏览器与监听事件

这两个接口上游仅在 iOS 实现:close() 关闭当前打开的浏览器,events() 返回重定向与关闭事件流。Dart 侧有 Platform.isIOS 守卫,其他平台调用 close() 是空操作、events() 返回空流:

// 仅 iOS 有效;Android 与鸿蒙上为空操作
await FlutterWebBrowser.close();

// 仅 iOS 有事件;其他平台返回空流
FlutterWebBrowser.events().listen((event) {
  if (event is RedirectEvent) {
    // 用户在浏览器里重定向到了新地址 event.url
  } else if (event is CloseEvent) {
    // 浏览器被关闭
  }
});

业务里若要订阅事件,建议参照上游 example 的守卫写法,把订阅逻辑限定在支持内嵌浏览器的平台:

/// Chrome Custom Tabs / SFSafariViewController only exist on Android & iOS.
/// On OpenHarmony the page always opens with the system browser and the
/// events stream is not available.
bool get _supportsBuiltInBrowser => Platform.isAndroid || Platform.isIOS;

5.6 跨平台一套代码

openWebPagewarmup 的调用代码在三个平台完全一致,无需任何平台分支:

平台openWebPage 行为warmupcloseevents
AndroidChrome Custom Tabs 内嵌标签页恒 true空操作空流
iOSSFSafariViewController预热服务关闭浏览器重定向 / 关闭事件
OpenHarmony / HarmonyOS拉起系统默认浏览器恒 true空操作空流

调用侧零分支是适配"只做加法"带给使用方的直接收益:同一份业务代码部署到鸿蒙,打开网页的入口自动可用,错误码语义各端对齐,onError 的判断写法不需要按平台修改。

5.7 实战:给应用加一个"用户协议"入口

把前面的内容拼起来,做一个真实的业务场景:设置页里的"用户协议与隐私政策"入口,点击后用系统浏览器打开协议页,打开失败时按错误码给出兜底提示:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_web_browser/flutter_web_browser.dart';

class SettingsPage extends StatelessWidget {
  const SettingsPage({super.key});

  Future<void> _openAgreement(BuildContext context) async {
    try {
      await FlutterWebBrowser.openWebPage(
        url: 'https://example.com/agreement.html',
      );
    } on PlatformException catch (e) {
      if (!context.mounted) return;
      final String message = switch (e.code) {
        'invalid_url' => '链接无效,请检查配置',
        'no_activity' => '未找到可用的浏览器应用',
        _ => '打开网页失败,请稍后重试',
      };
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text(message)));
    }
  }

  
  Widget build(BuildContext context) {
    return ListView(
      children: [
        ListTile(
          title: const Text('用户协议与隐私政策'),
          trailing: const Icon(Icons.chevron_right),
          onTap: () => _openAgreement(context),
        ),
      ],
    );
  }
}

这个入口在三个平台行为一致:鸿蒙与 Android 上拉起对应浏览器完成打开,iOS 上经 SFSafariViewController 打开;极端情况(设备无浏览器应用)下按错误码优雅降级,不会崩溃。

六、运行与验证

以下为 example 工程的实测记录。仓库中 example 的 Bundle Name 为 com.example.demo,自建工程验证时替换为你自己的包名即可。

设备项
设备HUAWEI ADA-AL10U 真机(nova 12 Ultra 星耀版)
系统版本HarmonyOS 6.1.0.135
API 版本24

6.1 构建并安装 example

在 example 目录构建、安装并启动:

cd example
flutter pub get
flutter build hap --debug

# 安装(需先在 DevEco Studio 中配置调试签名)
hdc install -r build/ohos/hap/entry-default-signed.hap

# 启动
hdc shell aa start -b com.example.demo -a EntryAbility

启动后主界面顶部显示 “Running on ohos”(见第一章图一)。

6.2 验证一:打开网页

点击 “Open Flutter website”,预期现象:系统默认浏览器立即拉起(见第一章图二),地址栏载入 flutter.io,页面重定向到 flutter.dev 并完整渲染(见第一章图三);返回应用无崩溃。也可观察日志确认无插件异常输出:

# 观察日志
hdc hilog | grep -iE "flutter"

6.3 验证二:预热浏览器服务

返回应用点击 “Warmup browser website”,预期现象:应用前台无任何变化、无崩溃,warmup() 的 Future 以 true 正常完成(见第一章图四)。这正是鸿蒙上的预期语义——拉起系统浏览器无需预热。

6.4 受限场景:定制参数被忽略

点击 OpenHarmony 演示段的 “Open Flutter website (options ignored on OHOS)” 按钮,该按钮调用时传入了 CustomTabsOptions(colorScheme: dark, showTitle: true, urlBarHidingEnabled: true)。预期现象:浏览器正常拉起,传入的定制参数不产生任何 UI 效果,也不报错。这是设计内行为(见 5.3):鸿蒙系统浏览器的外观不可由调用方定制,受限点如实呈现,不影响 url 的正常打开。

七、工作原理

整个调用链路如下:

Dart: FlutterWebBrowser.openWebPage(url)
  → 鸿蒙分支(_isOhos),通道消息只携带 url
    → MethodChannel('flutter_web_browser') / 'openWebPage'
      → ArkTS: FlutterWebBrowserPlugin.openWebPage
        → 校验 url 非空、UIAbility 已挂接
          → 组装 Want(action: ohos.want.action.viewData,
                      entities: [entity.system.browsable],
                      uri: url)
            → UIAbilityContext.startAbility(want)
              → 系统按 browsable entity 匹配,默认浏览器打开网页
                → startAbility 的 Promise 完成 → replyOnce → success(null)
                  → Dart 侧 Future 正常完成

Dart 侧按平台分发:鸿蒙上命中 _isOhos 分支,通道消息只带 url——鸿蒙系统浏览器不支持 Custom Tabs 的外观定制,其余参数没有传递的意义,native 侧的解析逻辑也因此保持最简。

ArkTS 侧组装的是鸿蒙官方推荐的"拉起浏览器打开网页"隐式 Want:viewData action 加 browsable entity 加 uri,系统按 entity 筛出所有声明可浏览能力的应用,由默认浏览器承接。发起拉起并回传结果的核心代码:

// MethodResult 只允许回复一次,标志位保证成功/失败路径互斥
let replied: boolean = false;
const replyOnce = (): void => {
  if (!replied) { replied = true; result.success(null); }
};
const replyError = (message: string, details: string): void => {
  if (!replied) {
    replied = true;
    result.error('no_activity', message, details);
  }
};
context.startAbility(want).then((): void => {
  replyOnce();                      // 对齐上游 Android:成功后 success(null)
}).catch((err: BusinessError): void => {
  replyError(`Failed to open ${url}: ${err.message}`, `${err.code}`);
});

错误码与触发条件:

错误码触发条件Dart 侧表现
invalid_urlurl 为空或未传PlatformException(code: invalid_url)
no_activity插件未挂接到 UIAbility,或 startAbility 拉起失败PlatformException(code: no_activity)

success(null) 与两个错误码的语义都与上游 Android 实现对齐,业务代码的异常处理在各平台写法一致。权限方面:拉起系统浏览器属于系统能力调用,无需在 module.json5 声明任何权限。

warmup 在鸿蒙侧一行 result.success(true):拉起系统浏览器无需预热,恒 true 让"先预热再打开"的既有代码模式原样成立。closeevents 上游仅 iOS 实现,Dart 侧 Platform.isIOS 守卫保证鸿蒙上根本不会发起这两个调用,native 侧 default 分支的 notImplemented 只是防御性兜底。

八、常见问题

Q1:flutter pub get 解析不到依赖?

先检查 ref:必须是 TAG 0.17.3-ohos-1.0.0-beta.1——写成上游版本号 0.17.3 会拉到没有鸿蒙适配的上游代码(构建 ohos 产物直接失败),写成不存在的 TAG 则依赖解析直接报错。再确认网络能访问 AtomGit。ref 也可以指向分支 feat/ohos_flutter_web_browser_0.17.3,但生产工程建议锁 TAG,保证版本可追溯。

Q2:构建 ohos 产物失败或安装报错?

编译需使用 ohos 版 Flutter SDK;框架版本要与 TAG 匹配(见第四章对照表);canary 引擎构建时宿主工程 compatibleSdkVersion 需为 "26.0.0",stable 引擎配真机取设备 API 之下的最近版本(本文为 6.1.0(23));hap 装不上先检查签名配置与 bundleName 是否匹配。这些环境类问题的定位过程见姊妹篇《flutter_web_browser 的鸿蒙适配教程》第四章。

Q3:openWebPageno_activity 怎么排查?

两种触发条件。插件未挂接到 UIAbility:标准 flutter create 生成的鸿蒙宿主工程不会出现,出现于手工改造过的宿主丢失了插件 Ability 注册的场景,检查宿主工程的插件注册链路。拉起失败:设备上无可处理网页的浏览器应用,量产设备极少见。业务侧按 5.7 的写法捕获 PlatformException 按错误码兜底即可。

Q4:warmup() 还需要调用吗?

可调可不调。鸿蒙上它恒返回 true,是刻意的空实现——为的是让上游"先预热再打开"的既有代码模式原样成立。新写的鸿蒙业务代码直接 openWebPage 即可。

Q5:能感知用户关闭了浏览器吗?

不能。events() 事件流仅上游 iOS 支持,鸿蒙上返回空流;openWebPage 的 Future 在浏览器拉起成功时就已完成,后续用户何时关闭、有没有看,没有任何回调。需要感知关闭行为的业务(如看完引导页才发奖励),鸿蒙侧应改用 Web 组件内嵌方案或自行设计流程,行为差异的完整讨论见姊妹篇《flutter_web_browser 的鸿蒙适配教程》4.2 节。

九、结语

回顾一下:在 pubspec.yaml 中以 git TAG 引入 flutter_web_browser,一行 FlutterWebBrowser.openWebPage(url: ...) 即可在鸿蒙 App 内唤起系统默认浏览器打开网页,无需平台分支、无需声明权限;warmup 保持既有代码模式成立,closeevents 维持上游的 iOS-only 语义,错误码与 Android 对齐。同一段代码在 Android、iOS 上也各自生效。插件已在 Flutter 3.41.10-ohos-1.0.1 搭配 HUAWEI 真机(HarmonyOS 6.1.0.135 / API 24)完整实测,真机上打开网页全链路验证通过。

使用中发现问题,欢迎到配套仓库提 Issue(鸿蒙适配层),插件上游行为的问题到原库 GitHub 仓库反馈,修复代码欢迎发 PR 共建。

相关链接

欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐