Flutter for OpenHarmony:三方库引入webview_flutter——网页内容嵌入与交互
场景一:新闻资讯应用需要展示富文本内容和网页文章场景二:电商应用需要嵌入 H5 活动页面和商品详情场景三:社交应用需要展示用户分享的网页链接场景四:混合开发应用需要复用现有的 Web 页面场景五:应用内需要展示帮助文档、用户协议等网页内容是 Flutter 官方提供的 WebView 插件!它支持加载网页、执行 JavaScript、监听页面事件等丰富功能,在 OpenHarmony 平台上基于鸿
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 前言:为什么需要 WebView?
在移动应用开发中,WebView 是连接原生应用与 Web 内容的桥梁:
场景一:新闻资讯应用需要展示富文本内容和网页文章
场景二:电商应用需要嵌入 H5 活动页面和商品详情
场景三:社交应用需要展示用户分享的网页链接
场景四:混合开发应用需要复用现有的 Web 页面
场景五:应用内需要展示帮助文档、用户协议等网页内容
webview_flutter 是 Flutter 官方提供的 WebView 插件!它支持加载网页、执行 JavaScript、监听页面事件等丰富功能,在 OpenHarmony 平台上基于鸿蒙原生 WebView 组件实现。
🚀 核心能力一览
| 功能特性 | 详细说明 | OpenHarmony 支持 |
|---|---|---|
| 加载网页 | 加载 URL 或 HTML 字符串 | ✅ |
| JavaScript 执行 | 执行 JS 代码并获取返回值 | ✅ |
| 页面导航 | 前进、后退、刷新等导航操作 | ✅ |
| 页面事件监听 | 监听页面加载、错误等事件 | ✅ |
| Cookie 管理 | 设置和获取 Cookie | ✅ |
| 用户代理设置 | 自定义 User-Agent | ✅ |
| 背景色设置 | 设置 WebView 背景颜色 | ✅ |
| 缩放控制 | 启用或禁用页面缩放 | ✅ |
| JavaScript 通道 | 原生与 Web 双向通信 | ✅ |
| 页面滚动控制 | 获取和设置滚动位置 | ✅ |
支持的功能
| 功能 | 说明 | OpenHarmony 支持 |
|---|---|---|
| WebViewController | WebView 控制器 | ✅ |
| WebViewWidget | WebView 组件 | ✅ |
| NavigationDelegate | 导航事件代理 | ✅ |
| WebViewCookieManager | Cookie 管理器 | ✅ |
| loadRequest | 加载 URL 请求 | ✅ |
| loadHtmlString | 加载 HTML 字符串 | ✅ |
| runJavaScript | 执行 JavaScript | ✅ |
| runJavaScriptReturningResult | 执行 JS 并返回结果 | ✅ |
| addJavaScriptChannel | 添加 JS 通道 | ✅ |
| goBack / goForward | 页面前进后退 | ✅ |
| reload | 刷新页面 | ✅ |
| clearCache | 清除缓存 | ✅ |
⚙️ 环境准备:
第一步:添加依赖
📄 pubspec.yaml:
dependencies:
flutter:
sdk: flutter
# 添加 webview_flutter 依赖(OpenHarmony 适配版本)
webview_flutter:
git:
url: https://atomgit.com/openharmony-tpc/flutter_packages.git
path: packages/webview_flutter/webview_flutter
执行命令:
flutter pub get
第二步:配置网络权限
WebView 需要网络权限才能加载网页。
📄 ohos/entry/src/main/module.json5:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
第三步:无需额外配置
webview_flutter 插件在 OpenHarmony 平台上无需额外配置,添加依赖和网络权限后即可使用。
📸 场景一:基础网页加载

📝 完整代码
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'WebView 基础示例',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2196F3)),
useMaterial3: true,
),
home: const BasicWebViewPage(),
);
}
}
class BasicWebViewPage extends StatefulWidget {
const BasicWebViewPage({super.key});
State<BasicWebViewPage> createState() => _BasicWebViewPageState();
}
class _BasicWebViewPageState extends State<BasicWebViewPage> {
late final WebViewController _controller;
int _loadingProgress = 0;
void initState() {
super.initState();
// 初始化 WebViewController
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// 更新加载进度
setState(() {
_loadingProgress = progress;
});
},
onPageStarted: (String url) {
print('页面开始加载: $url');
},
onPageFinished: (String url) {
print('页面加载完成: $url');
},
onWebResourceError: (WebResourceError error) {
print('加载错误: ${error.description}');
},
onNavigationRequest: (NavigationRequest request) {
// 可以在这里拦截特定 URL
if (request.url.startsWith('https://www.youtube.com/')) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse('https://flutter.dev'));
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('基础 WebView'),
actions: [
// 刷新按钮
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => _controller.reload(),
),
],
),
body: Column(
children: [
// 加载进度条
if (_loadingProgress < 100)
LinearProgressIndicator(
value: _loadingProgress / 100.0,
),
// WebView
Expanded(
child: WebViewWidget(controller: _controller),
),
],
),
);
}
}
🔑 关键点解析
- WebViewController:控制 WebView 的核心类,负责加载页面、执行 JS 等操作
- setJavaScriptMode:设置 JavaScript 模式,
unrestricted表示允许执行 JS - setNavigationDelegate:设置导航代理,监听页面加载事件
- onProgress:页面加载进度回调,范围 0-100
- onNavigationRequest:可以拦截特定 URL 的导航请求
- WebViewWidget:显示 WebView 的 Widget
🎨 场景二:JavaScript 交互与通信

📝 完整代码
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'WebView JS 交互',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF4CAF50)),
useMaterial3: true,
),
home: const JavaScriptWebViewPage(),
);
}
}
class JavaScriptWebViewPage extends StatefulWidget {
const JavaScriptWebViewPage({super.key});
State<JavaScriptWebViewPage> createState() => _JavaScriptWebViewPageState();
}
class _JavaScriptWebViewPageState extends State<JavaScriptWebViewPage> {
late final WebViewController _controller;
String _jsResult = '';
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.white)
..addJavaScriptChannel(
'FlutterChannel',
onMessageReceived: (JavaScriptMessage message) {
// 接收来自 Web 的消息
setState(() {
_jsResult = '来自 Web 的消息: ${message.message}';
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('收到消息: ${message.message}')),
);
},
)
..loadHtmlString(_getHtmlContent());
}
String _getHtmlContent() {
return '''
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 10px 0;
}
button {
background: #4CAF50;
color: white;
border: none;
padding: 15px 30px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
width: 100%;
}
button:active {
background: #45a049;
}
h2 {
margin-top: 0;
}
#result {
background: rgba(255, 255, 255, 0.2);
padding: 15px;
border-radius: 5px;
margin-top: 10px;
min-height: 50px;
}
</style>
</head>
<body>
<div class="container">
<h2>🌐 Web 与 Flutter 交互</h2>
<p>点击按钮测试 JavaScript 与 Flutter 的通信</p>
<button onclick="sendToFlutter()">📤 发送消息到 Flutter</button>
<button onclick="showAlert()">⚠️ 显示警告框</button>
<button onclick="changeColor()">🎨 改变背景色</button>
<div id="result"></div>
</div>
<script>
let colorIndex = 0;
const colors = [
'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)'
];
function sendToFlutter() {
const message = 'Hello from Web! Time: ' + new Date().toLocaleTimeString();
FlutterChannel.postMessage(message);
document.getElementById('result').innerHTML = '✅ 已发送: ' + message;
}
function showAlert() {
alert('这是一个来自 Web 的警告框!');
}
function changeColor() {
colorIndex = (colorIndex + 1) % colors.length;
document.body.style.background = colors[colorIndex];
document.getElementById('result').innerHTML = '🎨 背景色已更改';
}
// 接收来自 Flutter 的消息
function receiveFromFlutter(message) {
document.getElementById('result').innerHTML = '📥 来自 Flutter: ' + message;
}
</script>
</body>
</html>
''';
}
// 执行 JavaScript 代码
Future<void> _runJavaScript(String script) async {
try {
await _controller.runJavaScript(script);
} catch (e) {
print('执行 JS 失败: $e');
}
}
// 执行 JavaScript 并获取返回值
Future<void> _runJavaScriptReturningResult() async {
try {
final result = await _controller.runJavaScriptReturningResult(
'new Date().toLocaleString()',
);
setState(() {
_jsResult = 'Web 当前时间: $result';
});
} catch (e) {
print('执行 JS 失败: $e');
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('JavaScript 交互'),
),
body: Column(
children: [
// 控制按钮区域
Container(
padding: const EdgeInsets.all(16),
color: Colors.grey[100],
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton.icon(
onPressed: () {
_runJavaScript(
"receiveFromFlutter('Hello from Flutter!')",
);
},
icon: const Icon(Icons.send),
label: const Text('发送消息到 Web'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _runJavaScriptReturningResult,
icon: const Icon(Icons.access_time),
label: const Text('获取 Web 时间'),
),
if (_jsResult.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
_jsResult,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
// WebView
Expanded(
child: WebViewWidget(controller: _controller),
),
],
),
);
}
}
🔑 关键点解析
- addJavaScriptChannel:添加 JS 通道,Web 可以通过
FlutterChannel.postMessage()发送消息到 Flutter - runJavaScript:执行 JS 代码,无返回值
- runJavaScriptReturningResult:执行 JS 代码并获取返回值
- loadHtmlString:加载 HTML 字符串,适合展示本地内容
- 双向通信:Flutter 可以调用 Web 的函数,Web 也可以通过 Channel 发送消息到 Flutter
📊 场景三:页面导航与Cookie管理

📝 完整代码
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'WebView 导航示例',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFFF9800)),
useMaterial3: true,
),
home: const NavigationWebViewPage(),
);
}
}
class NavigationWebViewPage extends StatefulWidget {
const NavigationWebViewPage({super.key});
State<NavigationWebViewPage> createState() => _NavigationWebViewPageState();
}
class _NavigationWebViewPageState extends State<NavigationWebViewPage> {
late final WebViewController _controller;
final TextEditingController _urlController = TextEditingController();
bool _canGoBack = false;
bool _canGoForward = false;
String _currentUrl = '';
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) {
setState(() {
_currentUrl = url;
_urlController.text = url;
});
_updateNavigationState();
},
onPageFinished: (String url) {
_updateNavigationState();
},
),
)
..loadRequest(Uri.parse('https://flutter.dev'));
_urlController.text = 'https://flutter.dev';
}
// 更新导航按钮状态
Future<void> _updateNavigationState() async {
final canGoBack = await _controller.canGoBack();
final canGoForward = await _controller.canGoForward();
setState(() {
_canGoBack = canGoBack;
_canGoForward = canGoForward;
});
}
// 加载指定 URL
void _loadUrl() {
final url = _urlController.text.trim();
if (url.isNotEmpty) {
Uri? uri;
if (url.startsWith('http://') || url.startsWith('https://')) {
uri = Uri.parse(url);
} else {
uri = Uri.parse('https://$url');
}
_controller.loadRequest(uri);
}
}
// 设置 Cookie
Future<void> _setCookie() async {
final cookieManager = WebViewCookieManager();
await cookieManager.setCookie(
const WebViewCookie(
name: 'test_cookie',
value: 'flutter_webview',
domain: 'flutter.dev',
path: '/',
),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Cookie 已设置')),
);
}
}
// 清除 Cookie
Future<void> _clearCookies() async {
final cookieManager = WebViewCookieManager();
await cookieManager.clearCookies();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Cookie 已清除')),
);
}
}
// 清除缓存
Future<void> _clearCache() async {
await _controller.clearCache();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('缓存已清除')),
);
}
}
void dispose() {
_urlController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('页面导航'),
actions: [
PopupMenuButton<String>(
onSelected: (value) {
switch (value) {
case 'setCookie':
_setCookie();
break;
case 'clearCookies':
_clearCookies();
break;
case 'clearCache':
_clearCache();
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'setCookie',
child: Text('设置 Cookie'),
),
const PopupMenuItem(
value: 'clearCookies',
child: Text('清除 Cookie'),
),
const PopupMenuItem(
value: 'clearCache',
child: Text('清除缓存'),
),
],
),
],
),
body: Column(
children: [
// URL 输入栏
Container(
padding: const EdgeInsets.all(8),
color: Colors.grey[100],
child: Row(
children: [
Expanded(
child: TextField(
controller: _urlController,
decoration: const InputDecoration(
hintText: '输入网址',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
onSubmitted: (_) => _loadUrl(),
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.search),
onPressed: _loadUrl,
),
],
),
),
// 导航按钮栏
Container(
padding: const EdgeInsets.symmetric(vertical: 4),
color: Colors.grey[200],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: _canGoBack
? () => _controller.goBack()
: null,
),
IconButton(
icon: const Icon(Icons.arrow_forward),
onPressed: _canGoForward
? () => _controller.goForward()
: null,
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => _controller.reload(),
),
IconButton(
icon: const Icon(Icons.home),
onPressed: () {
_controller.loadRequest(
Uri.parse('https://flutter.dev'),
);
},
),
],
),
),
// 当前 URL 显示
if (_currentUrl.isNotEmpty)
Container(
padding: const EdgeInsets.all(8),
color: Colors.blue[50],
child: Row(
children: [
const Icon(Icons.link, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
_currentUrl,
style: const TextStyle(fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
// WebView
Expanded(
child: WebViewWidget(controller: _controller),
),
],
),
);
}
}
🔑 关键点解析
- canGoBack / canGoForward:检查是否可以前进或后退
- goBack / goForward:执行前进或后退操作
- reload:刷新当前页面
- WebViewCookieManager:管理 Cookie 的工具类
- setCookie:设置指定域名的 Cookie
- clearCookies:清除所有 Cookie
- clearCache:清除 WebView 缓存
📚 API 参考
WebViewController 方法
| 方法 | 返回值 | 说明 | OpenHarmony 支持 |
|---|---|---|---|
| loadRequest(Uri uri) | Future | 加载指定 URL | ✅ |
| loadHtmlString(String html) | Future | 加载 HTML 字符串 | ✅ |
| runJavaScript(String script) | Future | 执行 JavaScript | ✅ |
| runJavaScriptReturningResult(String) | Future | 执行 JS 并返回结果 | ✅ |
| addJavaScriptChannel(String, callback) | void | 添加 JS 通道 | ✅ |
| removeJavaScriptChannel(String) | Future | 移除 JS 通道 | ✅ |
| goBack() | Future | 后退 | ✅ |
| goForward() | Future | 前进 | ✅ |
| reload() | Future | 刷新页面 | ✅ |
| canGoBack() | Future | 是否可以后退 | ✅ |
| canGoForward() | Future | 是否可以前进 | ✅ |
| clearCache() | Future | 清除缓存 | ✅ |
| clearLocalStorage() | Future | 清除本地存储 | ✅ |
| setJavaScriptMode(JavaScriptMode) | Future | 设置 JS 模式 | ✅ |
| setBackgroundColor(Color) | Future | 设置背景色 | ✅ |
| setUserAgent(String?) | Future | 设置 User-Agent | ✅ |
| enableZoom(bool) | Future | 启用/禁用缩放 | ✅ |
| getScrollPosition() | Future | 获取滚动位置 | ✅ |
| scrollTo(int x, int y) | Future | 滚动到指定位置 | ✅ |
| scrollBy(int x, int y) | Future | 相对滚动 | ✅ |
| getTitle() | Future<String?> | 获取页面标题 | ✅ |
| currentUrl() | Future<String?> | 获取当前 URL | ✅ |
NavigationDelegate 回调
| 回调 | 参数类型 | 说明 | OpenHarmony 支持 |
|---|---|---|---|
| onPageStarted | String url | 页面开始加载 | ✅ |
| onPageFinished | String url | 页面加载完成 | ✅ |
| onProgress | int progress | 加载进度(0-100) | ✅ |
| onWebResourceError | WebResourceError | 资源加载错误 | ✅ |
| onNavigationRequest | NavigationRequest | 导航请求拦截 | ✅ |
| onUrlChange | UrlChange | URL 变化 | ✅ |
| onHttpAuthRequest | HttpAuthRequest | HTTP 认证请求 | ✅ |
WebViewCookieManager 方法
| 方法 | 返回值 | 说明 | OpenHarmony 支持 |
|---|---|---|---|
| setCookie(WebViewCookie) | Future | 设置 Cookie | ✅ |
| clearCookies() | Future | 清除所有 Cookie | ✅ |
💡 最佳实践
1. 安全的 JavaScript 执行
// 推荐:使用 try-catch 包裹 JS 执行
Future<void> safeRunJavaScript(String script) async {
try {
await controller.runJavaScript(script);
} catch (e) {
print('JS 执行失败: $e');
// 处理错误
}
}
// 推荐:验证返回值类型
Future<void> getPageInfo() async {
try {
final result = await controller.runJavaScriptReturningResult(
'document.title',
);
if (result is String) {
print('页面标题: $result');
}
} catch (e) {
print('获取标题失败: $e');
}
}
2. 处理页面加载错误
NavigationDelegate(
onWebResourceError: (WebResourceError error) {
// 根据错误类型显示不同提示
if (error.errorType == WebResourceErrorType.hostLookup) {
// 网络连接问题
showErrorDialog('网络连接失败,请检查网络设置');
} else if (error.errorType == WebResourceErrorType.timeout) {
// 超时
showErrorDialog('加载超时,请重试');
} else {
// 其他错误
showErrorDialog('加载失败: ${error.description}');
}
},
)
3. 优化加载性能
// 设置缓存策略
controller
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.white)
// 启用缩放以提升用户体验
..enableZoom(true);
// 预加载常用页面
Future<void> preloadPages() async {
await controller.loadRequest(Uri.parse('https://example.com'));
}
4. 内存管理
void dispose() {
// 清理资源
controller.clearCache();
controller.clearLocalStorage();
super.dispose();
}
⚠️ 常见问题
问题1:网页无法加载
错误信息:
WebResourceError: net::ERR_INTERNET_DISCONNECTED
解决方案:
- 检查网络权限配置(module.json5 中添加 INTERNET 权限)
- 确认设备网络连接正常
- 检查 URL 是否正确
问题2:JavaScript 无法执行
解决方案:
// 确保设置了 JavaScript 模式
controller.setJavaScriptMode(JavaScriptMode.unrestricted);
问题3:JavaScript Channel 无响应
解决方案:
// 1. 确保在 loadRequest 或 loadHtmlString 之前添加 Channel
controller
..addJavaScriptChannel('MyChannel', onMessageReceived: (message) {
print('收到消息: ${message.message}');
})
..loadRequest(uri);
// 2. Web 端确保使用正确的 Channel 名称
// JavaScript: MyChannel.postMessage('hello');
问题4:页面显示空白
解决方案:
// 1. 设置背景色
controller.setBackgroundColor(Colors.white);
// 2. 检查 HTML 内容是否正确
// 3. 查看控制台日志是否有错误信息
问题5:Cookie 设置不生效
解决方案:
// 在加载页面之前设置 Cookie
final cookieManager = WebViewCookieManager();
await cookieManager.setCookie(
WebViewCookie(
name: 'session',
value: 'token123',
domain: 'example.com',
path: '/',
),
);
// 然后再加载页面
await controller.loadRequest(Uri.parse('https://example.com'));
🎯 总结
通过本文,你已经掌握了:
✅ webview_flutter 插件的基本使用方法
✅ 加载网页和 HTML 字符串
✅ JavaScript 执行和双向通信
✅ 页面导航控制(前进、后退、刷新)
✅ Cookie 和缓存管理
✅ 页面加载事件监听
✅ 最佳实践和常见问题解决
webview_flutter 为 Flutter 应用提供了强大的 Web 内容嵌入能力,在 OpenHarmony 平台上基于鸿蒙原生 WebView 实现,性能优秀。开始在你的应用中使用 webview_flutter,打造丰富的混合应用体验吧!
📖 参考资源:
更多推荐

所有评论(0)