Flutter升级至最新版后WebView故障排查与组件更新咨询
Flutter 2.10.0升级后WebView故障修复及组件更新说明
问题背景
项目从Flutter 2.10.0升级至最新版本后,WebView无法正常运行,需明确两个核心问题:
- 升级涉及的WebView组件更新点
- 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注册异常,调整如下:
- 在
onWebViewCreated中直接注册监听,避免通过onSubscribeInAppWebSMSListener延迟注册 - 在
pubspec.yaml中锁定flutter_inappwebview最新稳定版:
flutter_inappwebview: ^6.0.0
- 更新
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
相关产品推荐
相关产品推荐

