Flutter与H5页面的交互

一、交互总览:H5 ↔ Flutter 有哪几条"通道"?

通道 方向 场景 核心 API
① JSChannel H5 → Flutter H5 主动把事件或数据回传给原生 addJavaScriptChannel()channel.postMessage() medium.com
② runJavaScript Flutter → H5 原生调用 JS 方法、读 DOM、注入脚本 runJavaScript() / runJavaScriptReturningResult() pub.dev
③ Navigation 拦截 双向 用 URL scheme 作为信号(支付宝、分享、关闭页等) NavigationDelegate.onNavigationRequest
④ postMessage (未来) H5 ↔ Flutter WebMessageChannel/Listener;目前官方还未完备,依赖社区或 InAppWebView inappwebview.dev

在 2025 年中,官方 webview_flutter稳妥做法仍是 ①+②+③ 。④ 正在讨论阶段,若需要 window.postMessage 全特性,可临时转用 flutter_inappwebview


二、最常用的"JSChannel"双向通信

1. Dart 侧注册通道

dart 复制代码
late final WebViewController ctrl;

@override
void initState() {
  super.initState();
  ctrl = WebViewController()
    ..addJavaScriptChannel(
      'NativeBridge',                         // JS 端调用的名字
      onMessageReceived: (msg) {
        final data = jsonDecode(msg.message); // {"type":"pay","payload":{...}}
        _handleFromWeb(data);
      },
    )
    ..loadRequest(Uri.parse('https://your.h5.com'));
}

2. H5 侧发送数据

html 复制代码
<script>
function notifyPaySuccess(orderId) {
  NativeBridge.postMessage(JSON.stringify({
    type: 'paySuccess',
    payload: { orderId }
  }));
}
</script>

3. Flutter 调 JS 并拿结果

dart 复制代码
final price = await ctrl
    .runJavaScriptReturningResult('getCartTotal()'); // JS 返回值自动转 Dart
print('总价 = $price 元');

三、导航拦截:最兼容但"笨一点"的方案

dart 复制代码
..setNavigationDelegate(
  NavigationDelegate(
    onNavigationRequest: (req) {
      if (req.url.startsWith('myapp://close')) {
        Navigator.pop(context);
        return NavigationDecision.prevent;
      }
      return NavigationDecision.navigate;
    },
  ),
)
  • 优点 :所有前端框架(Vue/React/Pure JS)都能 window.location.href = "myapp://close"
  • 缺点:只能传字符串,复杂参数需 Base64/JSON 编码。

四、实战技巧 & 深坑提示

主题 要点
参数打包 多字段请 JSON.stringify(obj),Dart 侧统一 jsonDecode,别用逗号拼接。
异步节流 JS 端高频事件(scroll、drag)不要直接 postMessage,前端 throttle 每 100 ms 发一次。
安全 只暴露白名单通道;runJavaScript 内容拼接用户输入前务必 jsonEncode
iOS Cookie iOS 14+ 第三方 Cookie 默认阻断,建议走同域访问或改 Token 方案。
Android 低端机卡顿 在瀑布流页面用 Virtual Display,会出现掉帧;可在启动页 WebViewPlatform.instance = SurfaceAndroidWebView(); 切 Hybrid Composition。
文件上传 需要 <input type="file">?别忘了在 Android 13+ 申请 READ_MEDIA_* 权限。

webview_flutter

webview_flutter 是官方维护、稳定性最佳的 Flutter WebView 插件。iOS 内部封装 WKWebView,Android 则直接调用系统 WebView优点 :跨端一致 API、可沉浸式集成;局限:依赖原生内核版本,体积不可避免增大 2--5 MB。

二、快速上手(4 步)

  1. 添加依赖

    bash 复制代码
    flutter pub add webview_flutter
  2. 导入包

    dart 复制代码
    import 'package:webview_flutter/webview_flutter.dart';
  3. 初始化 WebViewController

    dart 复制代码
    late final WebViewController controller;
    
    @override
    void initState() {
      super.initState();
      controller = WebViewController()
        ..setJavaScriptMode(JavaScriptMode.unrestricted)   // 允许 JS
        ..setBackgroundColor(const Color(0x00000000))      // 透明背景
        ..setNavigationDelegate(
          NavigationDelegate(
            onProgress:     (p)     => debugPrint('progress = $p%'),
            onPageStarted:  (url)   => debugPrint('start $url'),
            // onPageFinished: (url)   => _handleBackForbid(),
            onWebResourceError: (err) => debugPrint('error $err'),
            onNavigationRequest: (req) {
              // 拦截跳转示例
              if (req.url.startsWith('myapp://')) return NavigationDecision.prevent;
              return NavigationDecision.navigate;
            },
          ),
        )
        ..loadRequest(Uri.parse('https://www.geekailab.com'));
    }
  4. 渲染 WebView

    dart 复制代码
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: const Text('WebView Demo')),
        body: WebViewWidget(controller: controller),
      );
    }

三、常用 API 速览

功能 方法
JS 开关 setJavaScriptMode(JavaScriptMode)
背景色 setBackgroundColor(Color)
拦截导航 setNavigationDelegate(NavigationDelegate)
加载 URL/POST loadRequest(Uri, method, headers, body)
缩放开关 enableZoom(bool)
自定义 UA setUserAgent(String)
前进/后退/刷新 goBack(), goForward(), reload()
注入 JS 并取结果 runJavaScript(), runJavaScriptReturningResult()

除了注入 JS、拦截导航和 POST 请求以外,其余 API 多半是"锦上添花"。别为了"能用"把控制器写成一坨神对象,按需引用即可。

四、NavigationDelegate 深度解读

dart 复制代码
NavigationDelegate(
  onNavigationRequest: (req) { ... }, // 拦截跳转
  onPageStarted:  (url) { ... },
  onPageFinished: (url) { ... },
  onProgress:     (p)   { ... },      // 加载进度 0~100
  onWebResourceError: (err) { ... },  // 资源加载失败
  onUrlChange: (change) { ... },      // 重定向等
);
  • 防止某些 Scheme 跳转 :检查 req.url,遇到第三方支付、拨号等自定义协议可 prevent
  • H5 与原生交互 :H5 可通过改变 URL location.href = "myapp://close" 触发拦截,Native 再做动作。简单粗暴但最兼容。
  • 自定义错误页onWebResourceErrorcontroller.loadHtmlString(customHtml) 即可替换 404/SSL 失败页面。

示例演示

JS 与 Dart 双向通信

  • Dart → JSrunJavaScript('alert("hello")')

  • JS → Dart (iOS 专属 WKScriptMessageHandler / Android JavaScriptChannel):

    dart 复制代码
    controller.addJavaScriptChannel(
      'FlutterBridge',
      onMessageReceived: (msg) => print(msg.message),
    );

    JS 侧:FlutterBridge.postMessage('Hi from JS')

文件上传 / 下载

Android 13+ 需要在 AndroidManifest.xml 添加 <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>

如需自定义下载,可拦截 onNavigationRequest 中的 Content-Disposition: attachment,改由 diourl_launcher 处理。
3.

dart 复制代码
import 'package:webview_cookie_manager/webview_cookie_manager.dart';
final cookieManager = WebviewCookieManager();
await cookieManager.setCookie(
  url: 'https://your.site',
  name: 'token',
  value: 'abc123',
  domain: 'your.site',
);

Android Hybrid Composition(性能与兼容权衡)

  • Flutter 3.19 起默认启用 Virtual Display;复杂页面可强制 Hybrid:

    kotlin 复制代码
    WebViewPlatform.instance = SurfaceAndroidWebView(); // Android 主工程
  • 前瞻:官方 roadmap 计划在 2025 H1 合并两种模式,统一 GPU 合成路径,提升手势流畅度。

多窗口 / 弹窗

Web 侧 window.open 会触发 onCreateWindow(未开放 API)。目前只能拦截 URL 自行 launchUrl 到浏览器或新页面。
6.

安全与隐私

  • iOS 14+ 默认启用【智能防跟踪】,可能导致第三方 Cookie 失效。必要时后端改用 First-party Token。
  • Android WebView 会跟随系统更新,旧设备 < 7.0 建议提示升级或降级页面功能。

注意点

  • 异步加载状态 :搭配 ValueListenableBuilderRx 管理 progress,避免全局 setState()
  • 资源释放State.dispose() 中无需手动销毁 WebView,Controller 生命周期随 Widget 自动回收,但避免在列表中频繁创建/销毁
  • 滚动冲突 :对于嵌套在 NestedScrollView 内的 WebView,记得把外层设置 physics: const NeverScrollableScrollPhysics(),或使用 GestureDetector 透传。
  • iOS 导航返回 :若页面自带返回逻辑,先 if (await controller.canGoBack()) controller.goBack(); else Navigator.pop(context);
  • SEO / UA 伪装setUserAgent 可模拟 PC 浏览器,配合 meta viewport 可以获得更佳排版,但有时会触发广告/反爬虫检测。

小结

  • webview_flutter 能覆盖 90% H5 容器需求,但复杂 Hybrid App 仍需原生桥或第三方插件补位。
  • 未来趋势是 "Mini-Browser-as-a-Service" ------官方正在推进统一多媒体播放、下载管理和窗口控制接口;
  • 真正的坑常在"JS 与原生通信"与"多端 Cookie 同步"两处,尽量提前做兼容测试。

五、最小可运行 Demo(精简)

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

class WebDemoPage extends StatefulWidget {
  const WebDemoPage({super.key});
  @override
  State<WebDemoPage> createState() => _WebDemoPageState();
}

class _WebDemoPageState extends State<WebDemoPage> {
  late final WebViewController _c;

  @override
  void initState() {
    super.initState();
    _c = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel('NativeBridge',
          onMessageReceived: (m) => debugPrint('From JS: ${m.message}'))
      ..loadRequest(Uri.parse('https://flutter.dev'));
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(actions: [
      IconButton(
        icon: const Icon(Icons.code),
        onPressed: () =>
            _c.runJavaScript('NativeBridge.postMessage("Ping from Dart")'),
      )
    ]),
    body: WebViewWidget(controller: _c),
  );
}

结语

  • 项目上线前 Checklist:消息格式统一、权限声明、Cookie 测试、iOS back 手势与物理返回键兼容、弱网体验。
  • 如果需求只是"打开一个活动页 + 与原生分享/支付沟通",用 JSChannel + scheme 已足够。
  • 若要 复杂表单、长连接、离线缓存 ,采用 flutter_inappwebview
相关推荐
Jimmy23 分钟前
理解 React Context API: 实用指南
前端·javascript·react.js
李明一.1 小时前
Java 全栈开发学习:从后端基石到前端灵动的成长之路
java·前端·学习
观默1 小时前
我用AI造了个“懂我家娃”的育儿助手
前端·人工智能·产品
crary,记忆1 小时前
微前端MFE:(React 与 Angular)框架之间的通信方式
前端·javascript·学习·react.js·angular
星空寻流年1 小时前
javaScirpt学习第七章(数组)-第一部分
前端·javascript·学习
烛阴3 小时前
Python多进程开发实战:轻松突破GIL瓶颈
前端·python
爱分享的程序员3 小时前
前端面试专栏-主流框架:11. React Router路由原理与实践
前端·javascript·react.js·面试
weixin_459074353 小时前
在el-image组件的预览中添加打印功能(自定义功能)
前端·javascript·vue.js
知否技术3 小时前
2025微信小程序开发实战教程(三)
前端·微信小程序
海的诗篇_3 小时前
前端开发面试题总结-vue3框架篇(二)
前端·javascript·vue.js·面试·前端框架·vue