Flutter for OpenHarmony 实战:webview_flutter — 混合开发核心

前言

在现代 App 开发中,混合开发(Hybrid Development)是一种非常流行的模式。通过将部分业务逻辑或展示页面使用 Web 技术(HTML/CSS/JS)实现,可以极大地提高开发效率和动态更新能力。每个成熟的 App 几乎都离不开 WebView。

在 Flutter 生态中,webview_flutter 是官方维护的首选插件。好消息是,该插件已经适配了 OpenHarmony 平台,基于系统原生的 ArkWeb 内核封装,性能优异且功能完备。

本文将带领大家在 OpenHarmony 项目中集成 webview_flutter,实现网页加载、JS 交互以及简单的导航控制。

一、核心组件与架构

webview_flutter 采用了“Platform View”的机制,将原生的 OpenHarmony WebView 组件嵌入到 Flutter 的 Widget 树中。

1.1 WebViewController

这是控制 WebView 的核心类。从 4.0 版本开始,API 发生了重大重构,不再通过 Widget 参数初始化,而是通过创建一个 WebViewController 实例来配置和管理 WebView。

1.2 WebViewWidget

这是一个纯展示用的 Widget,它接受一个 WebViewController 实例,负责在界面上渲染 WebView。

二、安装与配置

2.1 添加依赖

pubspec.yaml 中添加:

dependencies:
  flutter:
    sdk: flutter
  # 请务必检查 pub.dev 或 OpenHarmony 社区以获取适配 OHOS 的最新版本
  webview_flutter: ^4.7.0
  
dependency_overrides:
 # WebView 全套覆盖,防止 Interface 版本冲突
  webview_flutter:
    git:
      url: "https://atomgit.com/openharmony-tpc/flutter_packages.git"
      path: "packages/webview_flutter/webview_flutter"
  webview_flutter_platform_interface:
    git:
      url: "https://atomgit.com/openharmony-tpc/flutter_packages.git"
      path: "packages/webview_flutter/webview_flutter_platform_interface"
  webview_flutter_ohos:
    git:
      url: "https://atomgit.com/openharmony-tpc/flutter_packages.git"
      path: "packages/webview_flutter/webview_flutter_ohos"

2.2 OpenHarmony 权限配置(必做)

WebView 加载网页属于网络操作,必须申请网络权限。打开 entry/src/main/module.json5

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}

此外,OpenHarmony 默认禁止明文流量(HTTP)。如果需要加载 HTTP 链接,需在 module.json5 中配置 requestPermissions 同级的 metadata 或查阅最新的网络安全配置指南(通常建议全站 HTTPS)。

在这里插入图片描述

三、基础用法:加载网页

3.1 初始化控制器

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  
  State<WebViewPage> createState() => _WebViewPageState();
}

class _WebViewPageState extends State<WebViewPage> {
  late final WebViewController _controller;

  
  void initState() {
    super.initState();
    // 1. 创建控制器
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted) // 允许执行 JS
      ..setBackgroundColor(const Color(0x00000000)) // 设置背景透明
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            debugPrint('加载进度: $progress%');
          },
          onPageStarted: (String url) {
            debugPrint('开始加载: $url');
          },
          onPageFinished: (String url) {
            debugPrint('加载完成: $url');
          },
          onWebResourceError: (WebResourceError error) {
            debugPrint('加载错误: ${error.description}');
          },
        ),
      )
      ..loadRequest(Uri.parse('https://openharmony.cn')); // 加载目标 URL
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('OpenHarmony 官网')),
      body: WebViewWidget(controller: _controller), // 2. 显示 WebView
    );
  }
}

在这里插入图片描述

四、进阶用法:JS 交互

WebView 的强大之处在于原生代码与网页脚本的交互。

4.1 Flutter 调用 JS

使用 runJavaScriptrunJavaScriptReturningResult

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  
  State<JsInteractionWebViewPage> createState() =>
      _JsInteractionWebViewPageState();
}

class _JsInteractionWebViewPageState extends State<JsInteractionWebViewPage> {
  late final WebViewController _controller;
  String _currentTitle = '等待获取...';

  
  void initState() {
    super.initState();

    // 构造一个简单的本地 HTML 用于演示 JS 交互
    // 包含 4.2 章节中提到的 button 代码
    const String htmlContent = '''
      <!DOCTYPE html>
      <html>
      <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>原始标题</title>
        <style>
          body { font-family: sans-serif; padding: 20px; text-align: center; }
          button { font-size: 18px; padding: 10px 20px; margin-top: 20px; background-color: #007AFF; color: white; border: none; border-radius: 5px; }
        </style>
      </head>
      <body>
        <h1>JS 交互演示</h1>
        <p id="msg">点击下方按钮调用 Flutter</p>
        
        <!-- 4.2 章节示例代码 -->
        <button onclick="Toaster.postMessage('我是来自 H5 的问候');">点击我调用 Flutter</button>
        
        <script>
          function changeColor() {
            document.body.style.backgroundColor = document.body.style.backgroundColor === 'lightblue' ? 'white' : 'lightblue';
          }
        </script>
      </body>
      </html>
    ''';

    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageFinished: (url) {
            debugPrint('页面加载完毕');
          },
        ),
      )
      // 4.2 配置 Channel
      ..addJavaScriptChannel(
        'Toaster', // Channel 名称,JS 端通过 window.Toaster 访问
        onMessageReceived: (JavaScriptMessage message) {
          if (!mounted) return;
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('H5 传来的消息: ${message.message}'),
              action: SnackBarAction(label: '确定', onPressed: () {}),
            ),
          );
        },
      )
      ..loadHtmlString(htmlContent); // 加载本地 HTML
  }

  // 4.1 Flutter 调用 JS
  Future<void> _changeWebTitle() async {
    await _controller.runJavaScript("document.title = 'Hello from Flutter!';");
    // 更新页面上的文字提示,以便直观看到效果
    await _controller.runJavaScript(
        "document.getElementById('msg').innerText = 'Flutter 修改了文档标题!';");
  }

  // 4.1 获取 JS 执行结果
  Future<void> _getWebTitle() async {
    // runJavaScriptReturningResult 返回的是 Object,通常是 String 或 num
    final result =
        await _controller.runJavaScriptReturningResult("document.title");
    setState(() {
      // 注意:返回的字符串可能包含引号,如 '"Hello from Flutter!"'
      _currentTitle = result.toString();
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('JS 交互深度演示 (章节4)')),
      body: Column(
        children: [
          // 操作控制区
          Container(
            padding: const EdgeInsets.all(16),
            color: Colors.grey[100],
            child: Column(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton(
                      onPressed: _changeWebTitle,
                      child: const Text('修改网页标题'),
                    ),
                    ElevatedButton(
                      onPressed: _getWebTitle,
                      child: const Text('获取网页标题'),
                    ),
                  ],
                ),
                const SizedBox(height: 12),
                Text('当前获取到的标题: $_currentTitle',
                    style: const TextStyle(
                        fontWeight: FontWeight.bold, color: Colors.blue)),
              ],
            ),
          ),
          const Divider(height: 1),
          // WebView 展示区
          Expanded(child: WebViewWidget(controller: _controller)),
        ],
      ),
    );
  }
}

在这里插入图片描述

五、OpenHarmony 平台适配细节

5.1 软键盘遮挡问题

在混合开发中,输入框被键盘遮挡是常见痛点。OpenHarmony 版本的 webview_flutter 通常已处理了 Resize 逻辑。建议在 Scaffold 中设置 resizeToAvoidBottomInset: true(默认即为 true)。

5.2 视频全屏播放

如果网页包含 <video> 标签,全屏播放可能需要额外的配置。请关注相关 Issue 或确保正确处理了 NavigationDelegate 中的请求。

5.3 性能调优

  • 复用机制:WebView 是重资源组件,尽量避免频繁创建和销毁。
  • 缓存:合理利用 clearCacheclearLocalStorage 管理存储空间。

六、完整示例代码

本示例演示了一个带有加载进度条和 JS 交互功能的简易浏览器。

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  
  State<FullFeatureWebView> createState() => _FullFeatureWebViewState();
}

class _FullFeatureWebViewState extends State<FullFeatureWebView> {
  late final WebViewController _controller;
  double _progress = 0.0;
  bool _initialized = false;

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

  void _initController() {
    // 实例化 Controller
    // 在实际开发中,可能需要根据平台 (Platform) 做不同的配置
    // 但 webview_flutter 4.0+ 已经做了很好的抽象
    final WebViewController controller = WebViewController();

    controller
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            if (!mounted) return;
            setState(() {
              _progress = progress / 100;
            });
          },
          onPageStarted: (String url) {
            debugPrint('开始加载: $url');
          },
          onPageFinished: (String url) {
            debugPrint('加载完成: $url');
          },
          onWebResourceError: (WebResourceError error) {
            debugPrint('加载错误: ${error.description}');
          },
          onNavigationRequest: (NavigationRequest request) {
            if (request.url.startsWith('https://www.youtube.com/')) {
              debugPrint('拦截了跳转: ${request.url}');
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      ..addJavaScriptChannel(
        'OnHarmony',
        onMessageReceived: (message) {
          if (!mounted) return;
          showDialog(
            context: context,
            builder: (ctx) => AlertDialog(
              title: const Text('JavaScript 消息'),
              content: Text(message.message),
              actions: [
                TextButton(
                  onPressed: () => Navigator.pop(ctx),
                  child: const Text('关闭'),
                )
              ],
            ),
          );
        },
      )
      ..loadRequest(Uri.parse('https://flutter.dev'));

    _controller = controller;
    _initialized = true;
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('OHOS WebView'),
        actions: [
          IconButton(
            icon: const Icon(Icons.arrow_back),
            onPressed: () async {
              if (_initialized && await _controller.canGoBack()) {
                await _controller.goBack();
              }
            },
          ),
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () {
              if (_initialized) _controller.reload();
            },
          ),
        ],
      ),
      body: Column(
        children: [
          if (_progress < 1.0)
            LinearProgressIndicator(value: _progress, color: Colors.blue),
          Expanded(
            child: _initialized
                ? WebViewWidget(controller: _controller)
                : const Center(child: CircularProgressIndicator()),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.code),
        onPressed: () {
          if (_initialized) {
            _controller.runJavaScript(
                "if(window.OnHarmony) { OnHarmony.postMessage('当前的 URL 是: ' + window.location.href) } else { alert('Channel 未就绪'); }");
          }
        },
      ),
    );
  }
}

在这里插入图片描述

七、总结

webview_flutter 在 OpenHarmony 上的表现已经相当成熟,足以支撑大部分混合开发场景。通过掌握 WebViewController 的配置和 JavaScriptChannel 的使用,开发者可以轻松构建功能丰富的 Web 容器应用。


🌐 欢迎加入开源鸿蒙跨平台社区:开源鸿蒙跨平台开发者社区

Logo

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

更多推荐