long time no see. 如果觉得该方案helps,点个赞,评论打个call,这是我前进的动力~
通常写法:
项目里用的webview_flutter
正常webview处理返回事件
if (await controller.canGoBack()) {controller.goBack();
} else {Navigator.pop(context);
}
就是h5历史栈,一直退栈,如果栈内元素只有一个了,就直接关闭webview的页面了。
问题描述:
正常情况是没问题的的。
比如A-->B-->C,一直触发返回事件的话,逻辑是C-->B,B-->A, A直接关。
如果h5里有重定向的话,就有问题了。
比如A(A1重定向到A2)-->B-->C,一直触发返回事件的话,逻辑是C-->B,B-->A2, A2-->A1-->A2,A2-->A1-->A2...
导致webview界面一直退不出来。
解决方案:
参考https://github.com/flutter/flutter/issues/137737,拉到最下面
设定pageFinished后xxx毫秒内NavigationRequest触发,判定为重定向。逻辑:已知A1重定向A2,此时触发返回事件,A2返回到A1,在A1准备重定向到A2的时候,根据条件判断为重定向然后进行阻断,并再次执行一次返回逻辑。
另外该issue原始代码还是有问题,没有考虑到NavigationRequest可能跑在onPageFinished前面,故自己添加了轮询等待的代码。
注意:这只是workaround,极端情况下并不能做到100%可靠。必要情况可以考虑跟h5相关开发,约定不用重定向或改用其它方案。
自己在android设备上实测了下,还是挺稳定的。
几种可以考虑的方案:
1.修改flutter_webview源码,上传到github,然后在自己的仓库引用该库。(该方案可以自己去修改到android测和ios测的相关代码,比如flutter_webview没提供忽略ssl证书报错和ssl证书检查的问题就可以通过该方式解决,感兴趣的话可以上网查一查)
2.换webview的库,比如用flutter_inappwebview,该库提供更强大的原生api支持,围绕这个库的api来尝试解决。也是很流行的库,但不是官方flutter.dev出品。
解决代码:
如下
class WebPageContainer extends StatefulWidget {const WebPageContainer({super.key});@overrideState<WebPageContainer> createState() => _WebPageContainerState();
}class _WebPageContainerState extends State<WebPageContainer> {late WebViewController controller;String url = '';bool _backEventTriggered = false;DateTime? _lastedPageFinishedTime;bool _pageIsFinished = false;@overridevoid initState() {super.initState();}@overridevoid didChangeDependencies() {final Map<String, dynamic>? arguments = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;if (arguments != null) {url = arguments['url'] ?? '';debugPrint('third---url:$url');}super.didChangeDependencies();_initWebViewController();}// web端调用// <button onclick="jump()">打开一个新的webpage</button>// function jump() {// var msg = "https://www.baidu.com"// if (toNewWebPage) {// toNewWebPage.postMessage(msg);// }// }// getStatusBarHeight用法// h5页面调用getStatusBarHeight,同上// h5页面同时要定义onStatusBarHeightReceived,该方法是flutter测获取完高度后调用的// 例如:// function onStatusBarHeightReceived(height) {// // 显示状态栏高度// document.getElementById('statusBarHeight').innerText = 'Status Bar Height: ' + height;// }void _initWebViewController() {controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted)..setBackgroundColor(const Color(0x00000000))..setNavigationDelegate(NavigationDelegate(onProgress: (int progress) {// debugPrint('WebPage onProgress $progress');},onPageStarted: (String url) {_pageIsFinished = false;debugPrint('WebPage onPageStarted $url');},onPageFinished: (String url) async {debugPrint('WebPage onPageFinished $url');_pageIsFinished = true;if (_backEventTriggered) {_lastedPageFinishedTime = DateTime.now();} else {_lastedPageFinishedTime = null;}},onWebResourceError: (WebResourceError error) {},onNavigationRequest: (NavigationRequest request) async {debugPrint('WebPage onNavigationRequest ${request.url}');debugPrint('WebPage onNavigationRequest isMainFrame ${request.isMainFrame}');//轮询,因为onNavigationRequest可能跑在onPageFinished前面,强制等待while (!_pageIsFinished) {await Future.delayed(Duration(milliseconds: 10));}if (_shouldApplyNavLockout()) {goBack(); //执行第二次backreturn NavigationDecision.prevent;}return NavigationDecision.navigate;},onUrlChange: (UrlChange change) {print('WebPage onUrlChange ${change.url}');}),)..addJavaScriptChannel('destoryCurrentPage', onMessageReceived: (JavaScriptMessage message) {//h5自己的返回键,返回到最后一步,当前页面出栈debugPrint('====destoryCurrentPage====');Nav.pop();})..addJavaScriptChannel('toNewWebPage', onMessageReceived: (JavaScriptMessage message) {//允许h5页面打开新的third_web_pageNav.push(routerName: RouterPathModuleCommon.WebPageContainer, arguments: {'url': message.message});})..addJavaScriptChannel('toLogin', onMessageReceived: (JavaScriptMessage message) {//login:有些h5页面跳转后需要登录的 logout:可能存在的h5页面提供登出功能Nav.push(routerName: RouterPathModuleAccount.LoginPage, arguments: {'url': message.message});})..addJavaScriptChannel('getStatusBarHeight', onMessageReceived: (JavaScriptMessage message) {double statusBarHeight = MediaQuery.of(context).padding.top;controller.runJavaScriptReturningResult("onStatusBarHeightReceived('$statusBarHeight')").then((value) => print("发送statusBarHeight成功"));});controller.loadRequest(Uri.parse(url));}// 判断重定向的条件: 最近一次pageFinished和navigationRequest小于xxx毫秒。 这只是个workaround,并不是十全十美的方案bool _shouldApplyNavLockout() {final timestamp = _lastedPageFinishedTime;_lastedPageFinishedTime = null;// TODO make the threshold time configurable.if (timestamp != null) {debugPrint('WebPage diff timestamp ${DateTime.now().difference(timestamp!)}');}return timestamp != null && DateTime.now().difference(timestamp) < const Duration(milliseconds: 150);}void goBack() async {if (await controller.canGoBack()) {_backEventTriggered = true;controller.goBack();} else {Navigator.pop(context);}}@overrideWidget build(BuildContext context) {return Scaffold(body: WillPopScope(onWillPop: () async {goBack();return false;},child: WebViewWidget(controller: controller),),);}
}