Flutter 里的 WebView,一旦开始承担支付和活动,就已经不是“一个网页容器”了

16 阅读7分钟

很多人一提到 Flutter 里的 WebView,第一反应还是“打开一个网页”。但我这几个月在真实项目里越做越明显地感觉到,一旦 WebView 开始承担支付、活动回传、统一跳转、预加载和崩溃善后,它本质上就已经不是一个页面容器了,而更像项目里的一块小平台。

很长一段时间里,我对 WebView 的理解都挺简单。

无非就是:

  • 打开一个网页
  • 给个标题
  • 处理一下返回

如果再复杂一点,也就是再加个登录态、加点参数、偶尔和原生交互一下。

但我后来在 Flutter 项目里连续做了几个月 WebView 相关的需求后,越来越不这么看了。

因为一旦它开始承担这些事情:

  • 支付
  • 活动回传
  • 路由跳转
  • 网页和 App 之间的消息桥接
  • 预加载
  • 渲染器崩溃善后

它就已经不再只是“打开一个网页”了。

它更像项目里的一块小平台。

1. 真正把 WebView 做复杂的,往往不是页面本身,而是它开始承担业务责任

很多 Flutter 项目里,WebView 一开始都挺轻。

产品说要接一个活动页,最自然的做法就是:

  • 给一个 URL
  • 打开页面
  • 显示完就算结束

这种阶段,WebView 的角色确实比较像“容器”。

但项目往前走一段时间后,它很容易被继续加责任:

  • 活动页里触发支付
  • 活动页里要跳原生页面
  • 页面回来后要继续回传结果
  • 网页要读 token、读顶部安全区、读页面入口
  • 页面挂了以后不能直接把用户扔死循环里

这时候你再把它只当成一个“网页容器”,通常就会越来越别扭。

因为真正复杂的,已经不是“能不能打开”,而是:

它已经站在网页、业务、支付、跳转和稳定性中间,开始承担系统责任了。

2. 我这次项目里的变化,其实很典型

这几个月我在 Flutter 项目里处理 WebView 相关需求时,看到的已经不是一个普通页面。

代码里它要做的事情非常具体。

比如在控制器里,Web 页面不是随便发一段消息过来就结束了,而是要把事件拆开处理:

switch (event) {
  case 'steamJump':
    _handleSteamJump(data['extra']);
    break;
  case 'pageJump':
    _handlePageJump(data['extra']);
    break;
  case 'androidpay':
    _handleAndroidPay(data['extra']);
    break;
  case 'routeJump':
    _handleRouteJump(data['extra']);
    break;
}

这段代码表面看只是一段事件分发判断,但它背后真正说明的事情是:

WebView 里跑的内容,已经不只是“显示给用户看”,而是在不断向 App 发起动作。

它要:

  • 跳 Steam 绑定
  • 跳协议页
  • 跳游戏详情
  • 拉起支付
  • 走统一路由协议

这时候你如果还把它当成一层“网页展示”,很多问题后面都会绕回来。

3. 真正难的不是网页和 App 能通信,而是要把这套通信当成长期能力来治理

这一层我后来感受特别明显。

项目里不只是单页通信,而是开始有一整套网页和 App 之间的消息桥接约定。

例如全局 Web 管理器里,会专门注册消息处理器,还要补一层兼容代码,保证网页侧的调用入口稳定:

controller.addJavaScriptHandler(
  handlerName: 'flutterMessage',
  callback: (args) {
    if (args.isEmpty) return null;
    final message = args.first.toString();
    _handleJavaScriptMessage(message);
    return null;
  },
);

同时还会主动给网页注入统一能力:

if (!window.flutterMessage) {
  window.flutterMessage = {
    postMessage: function(msg) {
      if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
        window.flutter_inappwebview.callHandler('flutterMessage', msg);
      }
    }
  };
}

这已经不是“让网页调一下原生方法”这么简单了。

它更像是在做一层桥接约定:

  • Web 端按这个协议发消息
  • App 端按这个协议接动作
  • 后续加新页面、新活动、新支付场景时,尽量还走这条线

也就是说,真正重要的不是“这次把某个活动页打通”,而是:

你有没有把 Web 和 App 之间的沟通,慢慢收成一套可长期复用的能力。

4. 一旦 WebView 接上支付,它的角色会再变一次

支付接入之后,WebView 的复杂度会明显上一个台阶。

因为这时候它不再只是:

  • 打开页面
  • 读点参数

而是开始进入一整条结果链。

项目里支付相关的处理,已经包括:

  • 网页请求 token
  • 读取安全区高度
  • 读取页面入口
  • 创建订单
  • 调起微信支付
  • 轮询订单状态
  • 再把结果回传给 Web 页面

像这种代码:

void _notifyPaymentResult(bool isSuccess, {String errorMessage = ''}) {
  _runJavaScript(_jsGetOrderStatus);
  final params = {
    'status': isSuccess ? 0 : 1,
    'order': orderInfo,
    'product': product,
    'errorMessage': errorMessage,
  };
  _runJavaScript(_jsGetOrderReturn, jsonEncode(params));
}

它背后的问题已经不是“WebView 能不能发起支付”,而是:

  • 成功后怎么回传
  • 失败后怎么回传
  • 用户返回时怎么处理
  • 中途页面切走怎么办
  • 哪些状态是网页要知道的

到这一步,WebView 已经不是容器了。

它在业务里扮演的,是一个:

承接外部流程、又要和 App 内部状态保持一致的中间层。

5. 预加载和崩溃善后,会进一步暴露 WebView 的“平台属性”

如果一个 WebView 只是偶尔打开一下,你一般不会太早考虑预加载,更不会太在意渲染器崩溃的善后逻辑。

但这个项目里,WebView 连这两件事都已经进来了。

全局 Web 管理器里,不只是打开页面,还会做不直接展示出来的后台预加载:

headlessWebView = HeadlessInAppWebView(
  initialUrlRequest: URLRequest(url: WebUri(url)),
  initialSettings: InAppWebViewSettings(
    cacheEnabled: true,
    domStorageEnabled: true,
    javaScriptEnabled: true,
  ),
  onRenderProcessGone: (controller, detail) {
    headlessWebView = null;
    preloadedUrl = null;
    isPreloaded.value = false;
  },
);

从工程视角看,这已经不是“一个页面怎么显示快一点”的问题了。

它讲的是:

  • 这个容器值不值得提前准备
  • 挂掉以后有没有统一兜底
  • 老实例要不要清理
  • 加载状态是不是要被明确管理

只要你开始做这些事,其实就已经说明:

这个 WebView 在项目里的地位,不再是附属页面,而是业务基础设施。

6. 我后来越来越明确:WebView 真正难的不是“打开网页”,而是把网页能力收进 App 结构里

这大概是我这几个月最明显的一个感受。

很多人会觉得,WebView 这种东西写到后面越来越麻烦,是因为:

  • 网页本身太复杂
  • 前后端配合麻烦
  • 网页和 App 之间的桥接容易出问题

这些当然都是真的。

但如果让我现在回头总结,我会觉得更本质的一层是:

你有没有意识到,这已经不是“把网页嵌进来”,而是在把一块业务平台接进来。

一旦意识不到这一点,后面就很容易发生这些情况:

  • 这次活动页单独补一个回传
  • 下次支付页再补一套桥接
  • 再下次又新开一个跳转协议
  • 崩了就单独修一页

表面上都能跑,最后整体会越来越乱。

反过来,如果一开始就承认它已经像平台,那后面的很多动作就会自然变成:

  • 协议统一
  • 状态统一
  • 回传统一
  • 跳转统一
  • 兜底统一

项目会稳很多。

7. 这件事对我最大的提醒,是别把复杂 WebView 问题继续当页面问题处理

我现在再看这类需求,会比以前更警惕一件事:

别把平台问题,当成页面问题补。

因为页面问题的思路通常是:

  • 这里坏了,修这里
  • 那里缺了,补那里

但 WebView 一旦已经承担支付、活动和路由,它后面暴露出来的很多问题,其实都不是某一个页面单独能讲清的。

它更像是在提醒你:

  • 这块能力已经变重了
  • 它应该被当成系统能力治理

所以如果现在让我用一句话总结这段经历,我会说:

Flutter 里的 WebView,一旦开始承担支付和活动,就已经不是“一个网页容器”了。

它更像一个小平台。
而真正难的,不是把它接进来,而是承认它已经变成平台之后,再用平台的方式去治理它。