给鸿蒙 App 增加打开外部网页能力 —— flutter_web_browser 的鸿蒙使用指南
给鸿蒙 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 版) | 3.41.10-ohos-1.0.1 |
| 编译 SDK | 6.1.0(23) |
| 设备 | HUAWEI ADA-AL10U 真机(nova 12 Ultra 星耀版,HarmonyOS 6.1.0.135 / API 24) |
两点提醒:
- 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
- 运行示例需要一台已签名配置好的鸿蒙真机。
四、引入依赖
进入工程目录,在 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.10 | 0.17.3-ohos-1.0.0-beta.1 | feat/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,
),
);
参数在各平台的生效情况:
| 参数组 | Android | iOS | 鸿蒙 |
|---|---|---|---|
| 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 跨平台一套代码
openWebPage 与 warmup 的调用代码在三个平台完全一致,无需任何平台分支:
| 平台 | openWebPage 行为 | warmup | close | events |
|---|---|---|---|---|
| Android | Chrome Custom Tabs 内嵌标签页 | 恒 true | 空操作 | 空流 |
| iOS | SFSafariViewController | 预热服务 | 关闭浏览器 | 重定向 / 关闭事件 |
| 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_url | url 为空或未传 | PlatformException(code: invalid_url) |
| no_activity | 插件未挂接到 UIAbility,或 startAbility 拉起失败 | PlatformException(code: no_activity) |
success(null) 与两个错误码的语义都与上游 Android 实现对齐,业务代码的异常处理在各平台写法一致。权限方面:拉起系统浏览器属于系统能力调用,无需在 module.json5 声明任何权限。
warmup 在鸿蒙侧一行 result.success(true):拉起系统浏览器无需预热,恒 true 让"先预热再打开"的既有代码模式原样成立。close 与 events 上游仅 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:openWebPage 抛 no_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 保持既有代码模式成立,close 与 events 维持上游的 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 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:
更多推荐

所有评论(0)