flutter 解决webview加载重定向h5页面 返回重复加载问题

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});

  @override
  State<WebPageContainer> createState() => _WebPageContainerState();
}

class _WebPageContainerState extends State<WebPageContainer> {
  late WebViewController controller;
  String url = '';
  bool _backEventTriggered = false;
  DateTime? _lastedPageFinishedTime;
  bool _pageIsFinished = false;
  @override
  void initState() {
    super.initState();
  }

  @override
  void 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(); //执行第二次back
                return 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_page
        Nav.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);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: WillPopScope(
        onWillPop: () async {
          goBack();
          return false;
        },
        child: WebViewWidget(controller: controller),
      ),
    );
  }
}
相关推荐
RichardLai8815 小时前
[Flutter学习之Dart基础] - 控制语句
android·flutter
louisgeek18 小时前
Flutter Channel 通信机制
flutter
浅忆无痕21 小时前
Flutter空安全最小必备知识
android·前端·flutter
亚洲小炫风1 天前
flutter 打包mac程序 dmg教程
flutter·macos
亚洲小炫风1 天前
flutter 桌面应用之系统托盘
flutter·系统托盘
亚洲小炫风1 天前
flutter 桌面应用之右键菜单
flutter·桌面端·右键菜单·contextmenu
louisgeek1 天前
Flutter Widget、Element 和 RenderObject 的区别
flutter
顾林海2 天前
Flutter 文本组件深度剖析:从基础到高级应用
android·前端·flutter
RichardLai882 天前
[Flutter学习之Dart基础] - Dart方法基础
flutter
RichardLai882 天前
[Flutter学习之Dart基础] - Dart 变量类型及声明
flutter