当前位置: 首页 > article >正文

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


http://www.kler.cn/a/418909.html

相关文章:

  • 大模型时代的人工智能基础与实践——基于OmniForce的应用开发教程
  • hive3.1.3安装及基本例子
  • 【Electron学习笔记(三)】Electron的主进程和渲染进程
  • 【ETCD】基于client v3对etcd的基本操作示例
  • Oracle, PostgreSQL 字符串排序不一致及调整
  • atoi函数的模拟实现
  • 电脑cpu带的字母代表啥
  • 牛客面经学习【2024/12/1】
  • 剪映自动批量替换视频、图片素材教程,视频批量复刻、混剪裂变等功能介绍
  • PDF版地形图矢量出现的问题
  • Linux下的root密码重置
  • Dockerfile打包部署
  • MYSQL 什么是内连接 外连接 左连接 右连接?及适用场景
  • C++11新增特性2
  • vue3typescript,shims-vue.d.ts中declare module的vue声明
  • C-操作符
  • Linux虚拟机安装nginx踩坑记录
  • 《UDS协议从入门到精通(UDS速查手册)》(完结撒花版)
  • Java之链表1
  • vue3+elPlus 选择框select 下拉数据过千条,页面卡顿,分页解决
  • Java中 HttpURLConnection 和 HttpClient 详解(初学者友好)
  • 【从零开始的LeetCode-算法】3208. 交替组 II
  • 【Git教程 之 版本控制】
  • 深入探讨SQL优化原理 - 增量查询和索引加速
  • JavaScript 高级教程:异步编程、面向对象与性能优化
  • EC2还原快照