欢迎加入开源鸿蒙跨平台社区: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),
          ),
        ],
      ),
    );
  }
}

🔑 关键点解析

  1. WebViewController:控制 WebView 的核心类,负责加载页面、执行 JS 等操作
  2. setJavaScriptMode:设置 JavaScript 模式,unrestricted 表示允许执行 JS
  3. setNavigationDelegate:设置导航代理,监听页面加载事件
  4. onProgress:页面加载进度回调,范围 0-100
  5. onNavigationRequest:可以拦截特定 URL 的导航请求
  6. 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),
          ),
        ],
      ),
    );
  }
}

🔑 关键点解析

  1. addJavaScriptChannel:添加 JS 通道,Web 可以通过 FlutterChannel.postMessage() 发送消息到 Flutter
  2. runJavaScript:执行 JS 代码,无返回值
  3. runJavaScriptReturningResult:执行 JS 代码并获取返回值
  4. loadHtmlString:加载 HTML 字符串,适合展示本地内容
  5. 双向通信: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),
          ),
        ],
      ),
    );
  }
}

🔑 关键点解析

  1. canGoBack / canGoForward:检查是否可以前进或后退
  2. goBack / goForward:执行前进或后退操作
  3. reload:刷新当前页面
  4. WebViewCookieManager:管理 Cookie 的工具类
  5. setCookie:设置指定域名的 Cookie
  6. clearCookies:清除所有 Cookie
  7. 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

解决方案

  1. 检查网络权限配置(module.json5 中添加 INTERNET 权限)
  2. 确认设备网络连接正常
  3. 检查 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,打造丰富的混合应用体验吧!


📖 参考资源:

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐