基础入门 Flutter for OpenHarmony:webview_flutter 内嵌浏览器详解

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 欢迎来到 Flutter for OpenHarmony 社区!本文将深入讲解 Flutter 中 webview_flutter 内嵌浏览器组件的使用方法,带你全面掌握在应用中嵌入网页、JS交互、页面导航等功能。


一、webview_flutter 组件概述

在 Flutter for OpenHarmony 应用开发中,webview_flutter 是一个非常强大的内嵌浏览器组件,它允许开发者在应用中嵌入完整的网页浏览功能。通过 WebView,可以实现混合开发、加载 H5 页面、与 JavaScript 交互等功能,是连接原生应用与 Web 技术的桥梁。

📋 webview_flutter 组件特点

特点 说明
跨平台支持 支持 Android、iOS、Web、OpenHarmony
完整浏览功能 支持页面导航、前进后退、刷新等
JS 交互 支持 Flutter 与 JavaScript 双向通信
自定义控制 支持自定义 UserAgent、Cookie、Headers 等
回调监听 支持页面加载、导航、错误等事件监听
灵活配置 支持多种渲染模式(Hybrid Composition、Virtual Display)

💡 使用场景:混合开发、H5页面嵌入、富文本展示、第三方登录、支付页面、协议页面等。


二、OpenHarmony 平台适配说明

2.1 兼容性信息

本项目基于 webview_flutter@4.13.0 开发,适配 Flutter 3.27.5-ohos-1.0.4。

2.2 支持的功能

在 OpenHarmony 平台上,webview_flutter 支持以下功能:

功能 说明 OpenHarmony 支持
加载 URL 加载网页链接 ✅ yes
加载 HTML 加载本地 HTML 内容 ✅ yes
页面导航 前进、后退、刷新 ✅ yes
JavaScript 交互 JS 调用 Flutter ✅ yes
Flutter 调用 JS 执行 JavaScript 代码 ✅ yes
Cookie 管理 设置和获取 Cookie ✅ yes
UserAgent 设置 自定义 UserAgent ✅ yes

三、项目配置与安装

3.1 添加依赖配置

首先,需要在你的 Flutter 项目的 pubspec.yaml 文件中添加 webview_flutter 依赖。

打开项目根目录下的 pubspec.yaml 文件,找到 dependencies 部分,添加以下配置:

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"

配置说明:

  • 使用 git 方式引用开源鸿蒙适配的 flutter_packages 仓库
  • url:指定 AtomGit 托管的仓库地址
  • path:指定 webview_flutter 包的具体路径
  • ref:指定适配 OpenHarmony 的分支版本
  • 本项目基于 webview_flutter@4.13.0 开发,适配 Flutter 3.27.5-ohos-1.0.4

⚠️ 重要:对于 OpenHarmony 平台,必须使用 git 方式引用适配版本,不能直接使用 pub.dev 的版本号。

3.2 下载依赖

配置完成后,需要在项目根目录执行以下命令下载依赖:

bash 复制代码
flutter pub get

执行成功后,你会看到类似以下的输出:

复制代码
Running "flutter pub get" in my_cross_platform_app...
Resolving dependencies...
Got dependencies!

运行 flutter pub get 时遇到 "File name too long"(文件名过长)问题。

打开 Git Bash运行 cmd(需将 Git 添加到环境变量中),执行以下命令:

复制代码
     git config --global core.longpaths true

3.3 权限配置

WebView 需要网络权限,确保在 OpenHarmony 配置中添加了网络权限:

ohos/entry/src/main/module.json5:

json 复制代码
{
  "module": {
    "requestPermissions": [
       {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:network_reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      }
    ]
  }
3.4 添加权限原因说明

打开 ohos/entry/src/main/resources/base/element/string.json 文件,添加权限原因的字符串:

json 复制代码
{
  "string": [
    {
      "name": "network_reason",
      "value": "使用网络"
    }
  ]
}

四、webview_flutter 基础用法

4.1 导入库

在使用 webview_flutter 之前,需要先导入库:

dart 复制代码
import 'package:webview_flutter/webview_flutter.dart';

4.2 创建基本 WebView

dart 复制代码
class SimpleWebView extends StatefulWidget {
  const SimpleWebView({super.key});

  @override
  State<SimpleWebView> createState() => _SimpleWebViewState();
}

class _SimpleWebViewState extends State<SimpleWebView> {
  late final WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse('https://www.baidu.com'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('WebView')),
      body: WebViewWidget(controller: _controller),
    );
  }
}

4.3 加载 URL

dart 复制代码
// 加载网络 URL
_controller.loadRequest(Uri.parse('https://www.example.com'));

// 加载带参数的 URL
_controller.loadRequest(
  Uri.parse('https://www.example.com/search').replace(
    queryParameters: {'q': 'flutter'},
  ),
);

4.4 加载 HTML 内容

dart 复制代码
// 加载 HTML 字符串
_controller.loadHtmlString('''
  <html>
    <head><title>本地页面</title></head>
    <body>
      <h1>Hello WebView</h1>
      <p>这是一个本地 HTML 内容</p>
    </body>
  </html>
''');

// 加载本地 HTML 文件
_controller.loadFlutterAsset('assets/index.html');

4.5 页面导航控制

dart 复制代码
// 后退
await _controller.goBack();

// 前进
await _controller.goForward();

// 刷新
await _controller.reload();

// 停止加载
await _controller.goBack();

五、常用 API 详解

5.1 WebViewController - 控制器

WebViewController 是 WebView 的核心控制器,提供所有控制方法:

dart 复制代码
final WebViewController controller = WebViewController();

5.2 主要配置方法

dart 复制代码
WebViewController controller = WebViewController()
  // 启用 JavaScript
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  
  // 设置导航代理
  ..setNavigationDelegate(NavigationDelegate(
    onProgress: (int progress) {
      // 页面加载进度
    },
    onPageStarted: (String url) {
      // 页面开始加载
    },
    onPageFinished: (String url) {
      // 页面加载完成
    },
    onWebResourceError: (WebResourceError error) {
      // 加载错误
    },
    onNavigationRequest: (NavigationRequest request) {
      // 导航请求拦截
      return NavigationDecision.navigate;
    },
  ))
  
  // 设置 UserAgent
  ..setUserAgent('MyApp/1.0')
  
  // 启用缩放
  ..enableZoom(true);

5.3 JavaScript 交互

Flutter 调用 JavaScript:

dart 复制代码
// 执行 JavaScript 并获取返回值
final result = await _controller.runJavaScript('document.title');
print('页面标题: $result');

// 执行无返回值的 JavaScript
_controller.runJavaScript('alert("Hello from Flutter")');

JavaScript 调用 Flutter:

dart 复制代码
// 注册 JavaScript 通道
_controller.addJavaScriptChannel(
  'FlutterChannel',
  onMessageReceived: (JavaScriptMessage message) {
    print('收到 JS 消息: ${message.message}');
  },
);

// JavaScript 端调用
// FlutterChannel.postMessage('Hello from JavaScript');
dart 复制代码
// 获取 Cookie
final cookies = await _controller.runJavaScript('document.cookie');

// 设置 Cookie
await _controller.runJavaScript('document.cookie = "name=value"');

六、实际应用场景

6.1 带进度条的 WebView

dart 复制代码
class ProgressWebView extends StatefulWidget {
  final String url;
  final String title;

  const ProgressWebView({
    super.key,
    required this.url,
    required this.title,
  });

  @override
  State<ProgressWebView> createState() => _ProgressWebViewState();
}

class _ProgressWebViewState extends State<ProgressWebView> {
  late final WebViewController _controller;
  int _loadingProgress = 0;
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(NavigationDelegate(
        onProgress: (int progress) {
          setState(() {
            _loadingProgress = progress;
          });
        },
        onPageStarted: (String url) {
          setState(() {
            _isLoading = true;
          });
        },
        onPageFinished: (String url) {
          setState(() {
            _isLoading = false;
          });
        },
      ))
      ..loadRequest(Uri.parse(widget.url));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () => _controller.reload(),
          ),
        ],
      ),
      body: Column(
        children: [
          if (_isLoading)
            LinearProgressIndicator(
              value: _loadingProgress / 100.0,
              backgroundColor: Colors.grey[200],
              valueColor: const AlwaysStoppedAnimation<Color>(Colors.blue),
            ),
          Expanded(
            child: WebViewWidget(controller: _controller),
          ),
        ],
      ),
    );
  }
}

6.2 带导航栏的 WebView

dart 复制代码
class NavigationWebView extends StatefulWidget {
  final String initialUrl;

  const NavigationWebView({super.key, required this.initialUrl});

  @override
  State<NavigationWebView> createState() => _NavigationWebViewState();
}

class _NavigationWebViewState extends State<NavigationWebView> {
  late final WebViewController _controller;
  bool _canGoBack = false;
  bool _canGoForward = false;
  String _currentUrl = '';

  @override
  void initState() {
    super.initState();
    _currentUrl = widget.initialUrl;
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(NavigationDelegate(
        onPageFinished: (String url) async {
          _currentUrl = url;
          _updateNavigationState();
        },
      ))
      ..loadRequest(Uri.parse(widget.initialUrl));
  }

  Future<void> _updateNavigationState() async {
    final canGoBack = await _controller.canGoBack();
    final canGoForward = await _controller.canGoForward();
    setState(() {
      _canGoBack = canGoBack;
      _canGoForward = canGoForward;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('WebView')),
      body: Column(
        children: [
          Expanded(
            child: WebViewWidget(controller: _controller),
          ),
          Container(
            padding: const EdgeInsets.all(8),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.1),
                  blurRadius: 4,
                ),
              ],
            ),
            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(widget.initialUrl)),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

6.3 JavaScript 双向通信

dart 复制代码
class JsInteractionWebView extends StatefulWidget {
  const JsInteractionWebView({super.key});

  @override
  State<JsInteractionWebView> createState() => _JsInteractionWebViewState();
}

class _JsInteractionWebViewState extends State<JsInteractionWebView> {
  late final WebViewController _controller;
  final TextEditingController _messageController = TextEditingController();
  String _receivedMessage = '';

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel(
        'FlutterChannel',
        onMessageReceived: (JavaScriptMessage message) {
          setState(() {
            _receivedMessage = message.message;
          });
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('收到 JS 消息: ${message.message}')),
          );
        },
      )
      ..loadHtmlString('''
        <html>
          <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <style>
              body { font-family: Arial; padding: 20px; }
              button { padding: 10px 20px; margin: 10px; font-size: 16px; }
              input { padding: 10px; font-size: 16px; width: 80%; }
            </style>
          </head>
          <body>
            <h1>Flutter 与 JS 交互</h1>
            <input type="text" id="messageInput" placeholder="输入消息">
            <br>
            <button onclick="sendToFlutter()">发送给 Flutter</button>
            <div id="result" style="margin-top: 20px; padding: 10px; background: #f0f0f0;"></div>
            <script>
              function sendToFlutter() {
                var message = document.getElementById('messageInput').value;
                FlutterChannel.postMessage(message);
              }
              function receiveFromFlutter(message) {
                document.getElementById('result').innerText = 'Flutter 说: ' + message;
              }
            </script>
          </body>
        </html>
      ''');
  }

  void _sendToJs() {
    final message = _messageController.text;
    _controller.runJavaScript("receiveFromFlutter('$message')");
    _messageController.clear();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('JS 交互')),
      body: Column(
        children: [
          Expanded(
            child: WebViewWidget(controller: _controller),
          ),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.1),
                  blurRadius: 4,
                  offset: const Offset(0, -2),
                ),
              ],
            ),
            child: Column(
              children: [
                if (_receivedMessage.isNotEmpty)
                  Text('最后收到: $_receivedMessage'),
                const SizedBox(height: 8),
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _messageController,
                        decoration: const InputDecoration(
                          hintText: '输入消息发送给 JS',
                          border: OutlineInputBorder(),
                        ),
                      ),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: _sendToJs,
                      child: const Text('发送'),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

6.4 URL 拦截与处理

dart 复制代码
class InterceptWebView extends StatefulWidget {
  const InterceptWebView({super.key});

  @override
  State<InterceptWebView> createState() => _InterceptWebViewState();
}

class _InterceptWebViewState extends State<InterceptWebView> {
  late final WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(NavigationDelegate(
        onNavigationRequest: (NavigationRequest request) {
          // 拦截特定 URL
          if (request.url.contains('tel:')) {
            // 处理电话链接
            _handleTelLink(request.url);
            return NavigationDecision.prevent;
          }
      
          if (request.url.contains('mailto:')) {
            // 处理邮件链接
            _handleMailLink(request.url);
            return NavigationDecision.prevent;
          }
      
          if (request.url.contains('payment')) {
            // 拦截支付页面,跳转到原生支付
            _handlePayment(request.url);
            return NavigationDecision.prevent;
          }
      
          return NavigationDecision.navigate;
        },
      ))
      ..loadRequest(Uri.parse('https://www.example.com'));
  }

  void _handleTelLink(String url) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('拨打电话: $url')),
    );
  }

  void _handleMailLink(String url) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('发送邮件: $url')),
    );
  }

  void _handlePayment(String url) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('跳转到原生支付页面')),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('URL 拦截')),
      body: WebViewWidget(controller: _controller),
    );
  }
}

6.5 自定义 UserAgent 和 Headers

dart 复制代码
class CustomWebView extends StatefulWidget {
  const CustomWebView({super.key});

  @override
  State<CustomWebView> createState() => _CustomWebViewState();
}

class _CustomWebViewState extends State<CustomWebView> {
  late final WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setUserAgent('MyApp/1.0 (Mobile; OpenHarmony)')
      ..loadRequest(
        LoadRequestParams(
          uri: Uri.parse('https://www.example.com'),
          headers: {
            'Authorization': 'Bearer token123',
            'X-Custom-Header': 'CustomValue',
          },
        ),
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('自定义配置')),
      body: WebViewWidget(controller: _controller),
    );
  }
}

七、完整示例代码

下面是一个完整的示例应用,展示了 webview_flutter 的各种用法:

dart 复制代码
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});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'WebView 示例',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF6366F1)),
        useMaterial3: true,
      ),
      home: const WebViewDemoPage(),
    );
  }
}

class WebViewDemoPage extends StatefulWidget {
  const WebViewDemoPage({super.key});

  @override
  State<WebViewDemoPage> createState() => _WebViewDemoPageState();
}

class _WebViewDemoPageState extends State<WebViewDemoPage> {
  late final WebViewController _controller;
  int _loadingProgress = 0;
  bool _isLoading = false;
  bool _canGoBack = false;
  bool _canGoForward = false;
  String _currentTitle = '';

  @override
  void initState() {
    super.initState();
    _initController();
  }

  void _initController() {
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(NavigationDelegate(
        onProgress: (int progress) {
          setState(() {
            _loadingProgress = progress;
          });
        },
        onPageStarted: (String url) {
          setState(() {
            _isLoading = true;
          });
        },
        onPageFinished: (String url) async {
          setState(() {
            _isLoading = false;
          });
          _updateNavigationState();
          final title = await _controller.getTitle();
          if (title != null) {
            setState(() {
              _currentTitle = title;
            });
          }
        },
        onWebResourceError: (WebResourceError error) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('加载失败: ${error.description}')),
          );
        },
      ))
      ..addJavaScriptChannel(
        'FlutterChannel',
        onMessageReceived: (JavaScriptMessage message) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('收到消息: ${message.message}')),
          );
        },
      )
      ..loadRequest(Uri.parse('https://www.baidu.com'));
  }

  Future<void> _updateNavigationState() async {
    final canGoBack = await _controller.canGoBack();
    final canGoForward = await _controller.canGoForward();
    setState(() {
      _canGoBack = canGoBack;
      _canGoForward = canGoForward;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_currentTitle.isEmpty ? 'WebView 示例' : _currentTitle),
        centerTitle: true,
        elevation: 0,
        actions: [
          PopupMenuButton<String>(
            onSelected: _handleMenuSelection,
            itemBuilder: (context) => [
              const PopupMenuItem(value: 'refresh', child: Text('刷新')),
              const PopupMenuItem(value: 'baidu', child: Text('百度')),
              const PopupMenuItem(value: 'bing', child: Text('必应')),
              const PopupMenuItem(value: 'github', child: Text('GitHub')),
              const PopupMenuItem(value: 'js_test', child: Text('JS交互测试')),
            ],
          ),
        ],
      ),
      body: Column(
        children: [
          if (_isLoading)
            LinearProgressIndicator(
              value: _loadingProgress / 100.0,
              backgroundColor: Colors.grey[200],
              valueColor: const AlwaysStoppedAnimation<Color>(Color(0xFF6366F1)),
            ),
          Expanded(
            child: WebViewWidget(controller: _controller),
          ),
          _buildBottomNavigationBar(),
        ],
      ),
    );
  }

  Widget _buildBottomNavigationBar() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
      decoration: BoxDecoration(
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.1),
            blurRadius: 4,
            offset: const Offset(0, -2),
          ),
        ],
      ),
      child: SafeArea(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            IconButton(
              icon: const Icon(Icons.arrow_back),
              onPressed: _canGoBack ? () => _controller.goBack() : null,
              color: _canGoBack ? const Color(0xFF6366F1) : Colors.grey,
            ),
            IconButton(
              icon: const Icon(Icons.arrow_forward),
              onPressed: _canGoForward ? () => _controller.goForward() : null,
              color: _canGoForward ? const Color(0xFF6366F1) : Colors.grey,
            ),
            IconButton(
              icon: const Icon(Icons.refresh),
              onPressed: () => _controller.reload(),
              color: const Color(0xFF6366F1),
            ),
            IconButton(
              icon: const Icon(Icons.home),
              onPressed: () => _controller.loadRequest(Uri.parse('https://www.baidu.com')),
              color: const Color(0xFF6366F1),
            ),
          ],
        ),
      ),
    );
  }

  void _handleMenuSelection(String value) {
    switch (value) {
      case 'refresh':
        _controller.reload();
        break;
      case 'baidu':
        _controller.loadRequest(Uri.parse('https://www.baidu.com'));
        break;
      case 'bing':
        _controller.loadRequest(Uri.parse('https://www.bing.com'));
        break;
      case 'github':
        _controller.loadRequest(Uri.parse('https://github.com'));
        break;
      case 'js_test':
        _loadJsTestPage();
        break;
    }
  }

  void _loadJsTestPage() {
    _controller.loadHtmlString('''
      <html>
        <head>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <style>
            body {
              font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
              padding: 20px;
              background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
              min-height: 100vh;
              margin: 0;
            }
            .container {
              background: white;
              border-radius: 16px;
              padding: 24px;
              box-shadow: 0 10px 40px rgba(0,0,0,0.2);
            }
            h1 { color: #333; margin-top: 0; }
            input {
              width: 100%;
              padding: 12px;
              border: 2px solid #e0e0e0;
              border-radius: 8px;
              font-size: 16px;
              box-sizing: border-box;
              margin-bottom: 16px;
            }
            input:focus {
              border-color: #6366F1;
              outline: none;
            }
            button {
              width: 100%;
              padding: 14px;
              background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
              color: white;
              border: none;
              border-radius: 8px;
              font-size: 16px;
              font-weight: bold;
              cursor: pointer;
            }
            button:active {
              transform: scale(0.98);
            }
            #result {
              margin-top: 20px;
              padding: 16px;
              background: #f5f5f5;
              border-radius: 8px;
              min-height: 50px;
            }
          </style>
        </head>
        <body>
          <div class="container">
            <h1>🚀 Flutter 与 JS 交互测试</h1>
            <input type="text" id="messageInput" placeholder="输入消息发送给 Flutter">
            <button onclick="sendToFlutter()">发送给 Flutter</button>
            <div id="result">等待消息...</div>
          </div>
          <script>
            function sendToFlutter() {
              var message = document.getElementById('messageInput').value || 'Hello!';
              FlutterChannel.postMessage(message);
              document.getElementById('result').innerText = '已发送: ' + message;
            }
            function receiveFromFlutter(message) {
              document.getElementById('result').innerText = 'Flutter 说: ' + message;
            }
          </script>
        </body>
      </html>
    ''');
  }
}

八、常见问题与解决方案

8.1 页面空白

问题原因:

  • 未启用 JavaScript
  • 网络权限未配置

解决方案:

dart 复制代码
_controller.setJavaScriptMode(JavaScriptMode.unrestricted);

8.2 HTTPS 混合内容无法加载

问题原因:

  • HTTPS 页面加载 HTTP 资源被阻止

解决方案:

在 OpenHarmony 配置中允许混合内容。

8.3 JavaScript 交互不生效

问题原因:

  • 未启用 JavaScript 模式
  • JavaScript 通道名称错误

解决方案:

dart 复制代码
_controller
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  ..addJavaScriptChannel('FlutterChannel', onMessageReceived: (message) {
    // 处理消息
  });

8.4 返回键无法后退

问题原因:

  • 未处理物理返回键

解决方案:

dart 复制代码
@override
Widget build(BuildContext context) {
  return WillPopScope(
    onWillPop: () async {
      if (await _controller.canGoBack()) {
        _controller.goBack();
        return false;
      }
      return true;
    },
    child: Scaffold(
      body: WebViewWidget(controller: _controller),
    ),
  );
}

九、总结

webview_flutter 是 Flutter for OpenHarmony 应用开发中实现混合开发的重要组件。通过本文的学习,我们掌握了:

  1. 基础用法:加载 URL、HTML 内容、页面导航
  2. JavaScript 交互:Flutter 与 JS 双向通信
  3. 事件监听:页面加载、导航、错误等事件
  4. URL 拦截:自定义处理特定链接
  5. 自定义配置:UserAgent、Headers、Cookie 等

💡 开发建议:使用 webview_flutter 时应注意:

  • 始终启用 JavaScript 模式
  • 处理好物理返回键
  • 注意内存管理,及时释放资源
  • 对于复杂交互,使用 JavaScript 通道通信
相关推荐
松叶似针1 小时前
Flutter三方库适配OpenHarmony【secure_application】— 插件功能全景与适配价值
flutter·harmonyos·鸿蒙
钛态2 小时前
Flutter for OpenHarmony:leak_tracker 自动监测内存泄漏,精准定位未释放对象(内存性能优化) 深度解析与鸿蒙适配指南
flutter·华为·性能优化·harmonyos
松叶似针2 小时前
Flutter三方库适配OpenHarmony【secure_application】— 开发环境与工具链准备
flutter·harmonyos
无巧不成书02182 小时前
RN鸿蒙教学|第2课时】Git进阶+React Native鸿蒙工程配置优化(多终端适配入门)
git·react native·harmonyos
钛态2 小时前
Flutter for OpenHarmony:formz 简化表单验证逻辑,分离 UI 与业务状态(声明式表单验证) 深度解析与鸿蒙适配指南
flutter·ui·华为·harmonyos
HwJack202 小时前
HarmonyOS APP ArkTS 中 aboutToDisappear 资源回收机制生命周期优化和性能调优小知识
ubuntu·华为·harmonyos
不爱吃糖的程序媛3 小时前
Puro 全面解析:比 FVM 更快更省的 Flutter 版本管理器(鸿蒙定制版首选)
flutter
阿林来了3 小时前
Flutter三方库适配OpenHarmony【flutter_speech】— Flutter Plugin 机制解析
flutter·harmonyos·鸿蒙
前端不太难3 小时前
为什么鸿蒙不再适用 Android 分层
android·状态模式·harmonyos