You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter升级至最新版后WebView故障排查与组件更新咨询

Flutter 2.10.0升级后WebView故障修复及组件更新说明

问题背景

项目从Flutter 2.10.0升级至最新版本后,WebView无法正常运行,需明确两个核心问题:

  1. 升级涉及的WebView组件更新点
  2. WebView故障的修复方案

涉及的WebView组件更新

你的项目同时使用官方webview_flutter和第三方flutter_inappwebview,两者在版本跨度中均有重大变更:

1. webview_flutter(官方组件)

  • 安卓端默认启用Hybrid Composition,无需手动设置WebView.platform = AndroidWebView()
  • API结构调整,部分回调、配置项的命名与参数逻辑变更
  • 针对安卓12+的WebView兼容性优化,权限、渲染逻辑做了适配调整

2. flutter_inappwebview(第三方组件)

大版本更新(如5.x→6.x)中:

  • WebMessageListener的注册逻辑、参数要求变更,旧代码的调用方式不再兼容
  • 安卓端缓存配置、渲染模式的默认值调整
  • 与最新Flutter版本的通道通信逻辑重构,旧代码易出现交互失效问题

故障修复方案

结合代码与错误截图(WebView加载异常、JS交互失效),按以下步骤修复:

步骤1:统一WebView组件(推荐)

当前安卓用flutter_inappwebview、iOS用webview_flutter的跨平台不一致方案,易引发兼容性问题。建议统一使用官方webview_flutter,目前它对两端的支持已足够完善。

步骤2:适配webview_flutter最新API

若保留iOS端的webview_flutter,需调整:

  • 删除initState中注释的安卓平台设置代码,最新版本无需手动配置
  • 检查WebViewController的缓存清理逻辑,确保clearCache()调用符合最新API要求

步骤3:修复flutter_inappwebview的JS交互逻辑(安卓端)

故障核心大概率是WebMessageListener注册异常,调整如下:

  1. 在onWebViewCreated中直接注册监听,避免通过onSubscribeInAppWebSMSListener延迟注册
  2. 在pubspec.yaml中锁定flutter_inappwebview最新稳定版:
flutter_inappwebview: ^6.0.0
  1. 更新WebMessageListener注册代码,适配最新API:
await controller.addWebMessageListener(
  inAppWebView.WebMessageListener(
    jsObjectName: "openWebViewPage",
    onPostMessage: (message, sourceOrigin, isMainFrame, replyProxy) {
      onHandleOpenWebView(message.toString());
    },
    allowedOriginRules: const ["*"], // 按需限制来源,这里允许所有
  ),
);

步骤4:修复缓存清理逻辑

dispose中同时清理两个组件缓存会触发异常(当前平台仅初始化一个控制器),修改为:

@override
void dispose() async {
  super.dispose();
  if (widget.args.clearCacheOnDispose) {
    if (Platform.isIOS) {
      await _controller.future.then((value) => value.clearCache());
    } else {
      await _inAppWebViewController.future.then((value) => value.clearCache());
    }
  }
}

步骤5:检查权限与配置

  • 安卓端:AndroidManifest.xml添加网络权限
<uses-permission android:name="android.permission.INTERNET" />
  • iOS端:若加载HTTP链接,Info.plist添加配置
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

相关代码及错误截图

原WebViewWidget代码

import 'dart:async';
import 'dart:convert';
import 'dart:io';

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

import 'package:flutter_inappwebview/flutter_inappwebview.dart' as inAppWebView;
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart' as webViewFlutter;

import '../helpers/extensions/string_ext.dart';
import '../providers/webview_provider.dart';
import '../screens/webview_screen.dart';
import '../screens/virtual_attendance_screen.dart';

class WebViewWidget extends StatefulWidget {
  const WebViewWidget(this.args, {Key? key}) : super(key: key);
  final WebViewArguments args;

  @override
  State<WebViewWidget> createState() => _WebViewWidgetState();
}

class _WebViewWidgetState extends State<WebViewWidget> {
  final Completer<webViewFlutter.WebViewController> _controller =
      Completer<webViewFlutter.WebViewController>();
  final Completer<inAppWebView.InAppWebViewController> _inAppWebViewController =
      Completer<inAppWebView.InAppWebViewController>();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance?.addPostFrameCallback((_) async {});

    // Enable virtual display.
    // if (Platform.isAndroid) {
    //   webViewFlutter.WebView.platform = webViewFlutter.AndroidWebView();
    // }
  }

  @override
  void dispose() async {
    super.dispose();
    if (widget.args.clearCacheOnDispose) {
      await _controller.future.then((value) => value.clearCache());
      await _inAppWebViewController.future.then((value) => value.clearCache());
    }
  }

  void updateIsLoading(bool value) {
    if (Provider.of<WebViewProvider?>(context, listen: false) != null) {
      Provider.of<WebViewProvider>(context, listen: false)
          .updateIsLoading(value);
    }
  }

  void updatePercentage(String value) {
    if (Provider.of<WebViewProvider?>(context, listen: false) != null) {
      Provider.of<WebViewProvider>(context, listen: false)
          .updatePercentage(value);
    }
  }

  void updateIsError(bool value) {
    if (Provider.of<WebViewProvider?>(context, listen: false) != null) {
      Provider.of<WebViewProvider>(context, listen: false).updateIsError(value);
    }
  }

  void onHandleOpenWebView(String url) {
    try {
      Map<String, dynamic> json = jsonDecode(url);
      if (json["url"] != null && json["url"] != StringExtension.emptyString) {
        Navigator.of(context)
            .pushNamed(
          WebViewScreen.routeName,
          arguments: WebViewArguments(
            json["title"],
            json["url"],
          ),
        )
            .then((_) async {
          await onSubscribeInAppWebSMSListener();
        });
      }
    } catch (_) {}
  }

  void onHandleOpenWebViewNoClearCache(String url) {
    try {
      Map<String, dynamic> json = jsonDecode(url);
      if (json["url"] != null && json["url"] != StringExtension.emptyString) {
        Navigator.of(context)
            .pushNamed(
          WebViewScreen.routeName,
          arguments: WebViewArguments(
            json["title"],
            json["url"],
            clearCacheOnDispose: false,
          ),
        )
            .then((_) async {
          await onSubscribeInAppWebSMSListener();
        });
      }
    } catch (_) {}
  }

  void onHandleOpenAttendanceCheck() {
    try {
      Navigator.of(context)
          .pushNamed(VirtualAttendanceScreen.routeName)
          .then((_) async {
        await onSubscribeInAppWebSMSListener();
      });
    } catch (_) {}
  }

  Future<void> onSubscribeInAppWebSMSListener() async {
    try {
      if (Platform.isAndroid) {
        await _inAppWebViewController.future.then((controller) async {
          try {
            await controller.addWebMessageListener(
                inAppWebView.WebMessageListener(
                    jsObjectName: "openWebViewPage",
                    onPostMessage:
                        (message, sourceOrigin, isMainFrame, replyProxy) {
                      onHandleOpenWebView(message.toString());
                    }));
          } catch (_) {}
          try {
            await controller.addWebMessageListener(
                inAppWebView.WebMessageListener(
                    jsObjectName: "openWebViewPageNoClearCache",
                    onPostMessage:
                        (message, sourceOrigin, isMainFrame, replyProxy) {
                      onHandleOpenWebViewNoClearCache(message.toString());
                    }));
          } catch (_) {}
          try {
            await controller.addWebMessageListener(
                inAppWebView.WebMessageListener(
                    jsObjectName: "openAttendanceCheck",
                    onPostMessage:
                        (message, sourceOrigin, isMainFrame, replyProxy) {
                      onHandleOpenAttendanceCheck();
                    }));
          } catch (_) {}
          try {
            await controller.addWebMessageListener(
                inAppWebView.WebMessageListener(
                    jsObjectName: "openURLLauncherExternal",
                    onPostMessage:
                        (message, sourceOrigin, isMainFrame, replyProxy) {
                      var messageData = jsonDecode(message.toString());
                      launchUrl(Uri.parse(messageData["url"].toString()),
                          mode: LaunchMode.externalApplication);
                    }));
          } catch (_) {}
        });
      }
    } catch (_) {}
  }

  Widget webViewRender(BuildContext context, WebViewArguments args) {
    if (Platform.isIOS) {
      return webViewFlutter.WebView(
        backgroundColor: args.transparentBackground ? Colors.transparent : null,
        zoomEnabled: false,
        initialUrl: args.webURL,
        javascriptMode: webViewFlutter.JavascriptMode.unrestricted,
        javascriptChannels: {
          webViewFlutter.JavascriptChannel(
              name: "openWebViewPage",
              onMessageReceived: (webViewFlutter.JavascriptMessage message) {
                onHandleOpenWebView(message.message);
              }),
          webViewFlutter.JavascriptChannel(
              name: "openWebViewPageNoClearCache",
              onMessageReceived: (webViewFlutter.JavascriptMessage message) {
                onHandleOpenWebViewNoClearCache(message.message);
              }),
          webViewFlutter.JavascriptChannel(
              name: "openAttendanceCheck",
              onMessageReceived: (webViewFlutter.JavascriptMessage message) {
                onHandleOpenAttendanceCheck();
              }),
          webViewFlutter.JavascriptChannel(
              name: "openURLLauncherExternal",
              onMessageReceived: (webViewFlutter.JavascriptMessage message) {
                var messageData = jsonDecode(message.message.toString());
                launchUrl(Uri.parse(messageData["url"].toString()),
                    mode: LaunchMode.externalApplication);
              }),
        }.toSet(),
        onWebViewCreated: (webViewFlutter.WebViewController webViewController) {
          _controller.complete(webViewController);
        },
        onPageStarted: (value) {
          updateIsLoading(true);
        },
        onPageFinished: (value) {
          updateIsLoading(false);
        },
        onProgress: (value) {
          updatePercentage(value.toString());
        },
        onWebResourceError: (value) {
          updateIsError(true);
        },
      );
    } else {
      return inAppWebView.InAppWebView(
        initialUrlRequest: inAppWebView.URLRequest(url: Uri.parse(args.webURL)),
        initialOptions: inAppWebView.InAppWebViewGroupOptions(
          crossPlatform: inAppWebView.InAppWebViewOptions(
              javaScriptEnabled: true,
              javaScriptCanOpenWindowsAutomatically: true,
              useOnDownloadStart: true,
              useOnLoadResource: true,
              transparentBackground: args.transparentBackground,
              cacheEnabled: false,
              clearCache: args.isClearCache),
          android: inAppWebView.AndroidInAppWebViewOptions(
            // useHybridComposition: true,
            cacheMode: inAppWebView.AndroidCacheMode.LOAD_NO_CACHE,
          ),
          ios: inAppWebView.IOSInAppWebViewOptions(
            allowsInlineMediaPlayback: true,
          ),
        ),
        onWebViewCreated: (controller) async {
          // await controller.addWebMessageListener(
          //     inAppWebView.WebMessageListener(
          //         jsObjectName: "openWebViewPage",
          //         onPostMessage:
          //             (message, sourceOrigin, isMainFrame, replyProxy) {
          //           onHandleOpenWebView(message.toString());
          //         }));
          // await controller.addWebMessageListener(
          //     inAppWebView.WebMessageListener(
          //         jsObjectName: "openWebViewPageNoClearCache",
          //         onPostMessage:
          //             (message, sourceOrigin, isMainFrame, replyProxy) {
          //           onHandleOpenWebViewNoClearCache(message.toString());
          //         }));
          // await controller.addWebMessageListener(
          //     inAppWebView.WebMessageListener(
          //         jsObjectName: "openAttendanceCheck",
          //         onPostMessage:
          //             (message, sourceOrigin, isMainFrame, replyProxy) {
          //           onHandleOpenAttendanceCheck();
          //         }));
          _inAppWebViewController.complete(controller);
          await onSubscribeInAppWebSMSListener();
        },
        onLoadStart: (controller, url) {
          updateIsLoading(true);
        },
        onLoadStop: (controller, url) {
          updateIsLoading(false);
        },
        onProgressChanged: (controller, progress) {
          updatePercentage(progress.toString());
        },
        onLoadError: (controller, url, code, message) {
          updateIsError(true);
        },
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return webViewRender(context, widget.args);
  }
}

错误截图

错误截图


内容的提问来源于stack exchange,提问作者Ropon Pov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 21:55:54