返回顶部
首页 > 资讯 > 精选 >Flutter WebView预加载如何实现
  • 664
分享到

Flutter WebView预加载如何实现

2023-06-30 15:06:20 664人浏览 泡泡鱼
摘要

本文小编为大家详细介绍“Flutter WEBView预加载如何实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Flutter WebView预加载如何实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来

本文小编为大家详细介绍“Flutter WEBView预加载如何实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Flutter WebView预加载如何实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    背景

    WebView是在APP中,可以很方便的展示web页面,并且与web交互APP的数据。方便,并且更新内容无需APP发布新版本,只需要将最新的web代码部署完成,用户重新刷新即可。

    在WebView中,经常能够听到的一个需求就是:减少首次白屏时间,加快加载速度。因为加载web页面,必然会受到网络状况等的影响,无法像原生内容一样把静态内容秒加载出来。

    分析

    在原生AndroidiOS中,有一种预缓存资源,并在加载时拦截web请求,将事先缓存好的资源替换上去,从而实现预加载的方案。

    • ioS常见的拦截的框架是Cocoahttpserver / Telegraph

    • Android则是在WebViewClientshouldInterceptRequest去进行拦截

    道理都是一样的。

    那么,Flutter有没有类似的方式去实现预加载web资源呢?

    有!类似iOS中的CocoaHttpServer,flutter也有一个HttpServer,可以发现,他们基本是一样的功能,并且Flutter HttpServer支持Android和iOS。

    HttpServer

    HttpServer包含在http的包中,在pub.dev找到最新的版本加入即可。

    dependencies:  flutter:    sdk: flutter  http: ^0.13.4

    权限要求

    因为要http服务,所以需要配置一下允许各平台的http请求。

    启动服务

    abstract class HttpServer implements Stream<HttpRequest>
    var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);

    HttpServer.bind方法会开启侦听对应Address的请求,第一个入参address可以自定,第二个port可以为0,也可以自定,为0的话,则由系统随机分配一个临时端口

    异步返回一个HttpServer,可以拿到最终的地址,也可以配置一些属性

    curAddresses = _server!.address.address;curPort = _server!.port;_server!.sessionTimeout = 60;

    并且,可以设置拦截侦听!

    serverSub = _server!.listen(_responseWebViewReq, onError: (e) => log(e, name: _logKey));

    listen即常见的StreamSubscription,关闭时需要Cancel。 在listen的onDate中,会提供一个HttpRequest,即被拦截的请求的HttpRequest。

    _responseWebViewReq(HttpRequest request)

    我们可以取得其当前请求的Uri,并且可以根据不同的Uri,返回不同的结果给到该请求的response

    var uri = request.requestedUri;final data = await _getResponseData(uri);request.response.add(data);

    也可以设置headers

    request.response.headers.add('Content-Type', '$mime; charset=utf-8');

    finally,在所有请求结束时,关闭该response

    request.response.close();

    至此,HttpServer拦截的功能就实现了。

    接下来?

    当然仅仅实现HttpServer拦截是不够的,既然我们要实现预加载,最主要的拦截方案已经有了,那么,接下来就需要考虑,资源的配置,资源的下载和存储,版本的管理,如何根据实际url获取对应HttpServer bind的url等。不在意的话也可以直接跳到最后看Demo。

    PS:因为项目中命名为LocalServerWebview,所以后面代码中可能称其为LocalServer。

    资源配置

    我们需要知道,哪些资源是需要被下载的,被使用在LocalServer服务中的。所以我设计了一个JSON配置文件,存储在服务端中,每次打开App时下发。大致的格式为:

    {    "option": [        {            "key": "test",            "open": 1,            "priority": 0,            "version": "20222022"        },        {            "key": "test2",            "open": 0,            "priority": 0,            "version": "20222222"        }    ],    "assets": {        "test": {            "compress": "/local-server/test.zip"        },        "test2": {            "compress": "/local-server/test2.zip"        }    },    "basics": {        "common": {            "compress": "/local-server/common.zip",            "version": "20220501"        }    },    "local_server_open": 1}

    主要根据我这边的web项目配置,option为配置的对应webPath的开关下载优先级版本号

    assets中则是option对应的key的压缩包地址(也可以一起写在option中,不过实际业务中还有别的配置,所以就这样吧)

    basics则是统一资源的配置,比如common,所有web通用的js、json资源等,便统一下载,避免重复。

    local_server_open是总开关,关闭时则LocalServer服务不会使用。

    然后便是获取到配置后,对符合条件的资源进行下载解压和存储。

    // 触发basics预下载LocalServerDownloadService.instance.preloadBasicsData(json['basics'], basics, oldBasic);
    // 触发assets预下载LocalServerDownloadService.instance.preloadAssetsData(_diffAssets(value, assets));

    下载解压与本地存储

    这边使用的Dio进行download,

    Dio().download(queueItem.zipUrl, zipPath).then((resp) {  if (resp.statusCode != 200) {    _log('下载ls 压缩包失败  err:${resp.statusCode} zipUrl:${queueItem.zipUrl}');    throw Exception('下载ls 压缩包失败  err:${resp.statusCode}');  }  return unarcHive(queueItem, zipPath);})

    archive包进行解压

    // 找到对应zipUrl的本地文件路径Directory saveDirct = LocalServerConfiguration.getCurrentZipPathSyncDirectory(item.zipUrl);final zipFile = File(downPath);if (!zipFile.existsSync()) {  throw Exception('Local server 下载包文件路径不存在:$downPath');}List<int> bytes = zipFile.readAsBytesSync();Archive archive = ZipDecoder().decodeBytes(bytes);···// 清理之前的缓存File oldfile = File(downPath);if (oldfile.existsSync()) {  oldfile.deleteSync();}

    zip文件在解压完成后会被清理,根据zipUrl来决定存储的文件路径。 若已经存在资源,则无需下载。

    若是下载失败的话,会被标记为failure,在重启app后的新下载任务中会重新尝试。 也可以加个重试几次的逻辑。

    queueItem.loadState = LoadStateType.failure;queueItem.downloadCount += 1;

    版本管理与更新

    在配置json中可以看到version相关的设置,在上一步的下载解压完成之后,会把文件状态、对应的option、assets、basics数据(版本)存储起来。

    首先检查对应的版本号是否能对上,若对不上的话,旧的数据将不会用来去重,而是直接使用最新获取到的配置进行下载和覆盖。

    // 处理 assets 资源,和版本控制LocalServerConfiGCache.getOptions().then((oldOptions) {  // assets 缓存和版本处理  LocalServerConfigCache.getAssets().then((value) {    var oldAssets = value;    // 版本不对,则移除,并需要下载    if (oldOptions != null) {      for (var e in oldOptions) {        var res = options.where((element) => element.key == e.key);        if (res.isNotEmpty && res.first.version != e.version) {          _log('资源 ${e.key} 需要更新');          oldAssets?.removeWhere((key, value) => key == e.key);        }      }    }    // 触发预下载    LocalServerDownloadService.instance.preloadAssetsData(_diffAssets(value, assets));  **});**});

    在预下载加入下载队列前,会检查之前存储的文件状态,若是suceess,则跳过不进行下载。

    _assetsBucket.forEach((key, value) {  for (var tmpItem in value) {    switch(tmpItem.loadState) {      case LoadStateType.unLoad:      case LoadStateType.loading:        _addQueue(tmpItem);        break;      case LoadStateType.success:        sucCount++;        break;      case LoadStateType.failure:        _addQueue(tmpItem);        break;    }  }});

    获取LocalServer Url并加载Webview

    打开Webview前,则需要打开LocalServer服务,并且可以根据不同的url获取得到对应的LocalServerUrl

    return LocalServerService.instance.getLocalServerWebUrl(h6Path, query.isEmpty ? path : path + '?' + query);
    String _getLocalServerWebUrl(String oriUrl, String localServerKey) {  return 'http://${curAddresses ?? InternetAddress.loopbackIPv4.address}:$curPort$localServerKey';}

    其实就是在bind成功之后,将addressport存储下来,并在获取的时候将query与其拼接。

    然后将处理后的url给到webview进行加载,即会触发

    这里有个处理是将basics统一资源的链接,动态的添加到每个web页面的资源列表里。Binder在初始化配置和资源下载完成后,会存储ConfigbasicCache到内存中。并且统记webpage打开数量,避免HttpServer还在使用时被关闭。

    @overridevoid initState() {  super.initState();  log('页面开始加载:${DateTime.now()}', name: 'web-time');  _localServerBuilder = LocalServerCacheBinder()..initBinder();  LocalServerWebViewManager.instance.reGISterBuilder(_localServerBuilder);  _innerUrl = _localServerBuilder.convertH5Url2LocalServerUrl(widget.url);}

    WebView

    WebView(  initialUrl: _innerUrl,  debuggingEnabled: true,  ···)

    兜底措施

    会存在些情况就是,预加载的资源还没有下载解压完成或者说资源下载失败了,用户就开启了Webview,这时候我们就需要用源链接(baseDomain)去实时获取到数据来替换,避免web页面异常。

    // 找不到本地文件,使用网络下载拿到原始数据var nowUri = request.requestedUri;var baseDomain = LocalServerCacheBinderSetting.instance.baseDomain;var baseUri = Uri.parse(baseDomain);// 替换为原始urlnowUri = nowUri.replace(    scheme: baseUri.scheme, host: baseUri.host, port: baseUri.port);// dio请求,responseType 必须是bytesvar res = await Dio().getUri(nowUri, options: Options(responseType: ResponseType.bytes));data = res.data;name = basename(nowUri.path.split('/').toList().last);mime = lookupMimeType(name);request.response.headers.add('Content-Type', '$mime; charset=utf-8');return data;

    统一管理

    最终所有的模块由一个manager进行统一管理,继承LocalServerClientManger,设置相应的初始化和配置即可。

    class LocalServerClientManager implements LocalServerStatusHandler,    LocalServerDownloadServiceProtocol
    class LocalServerWebViewManager extends LocalServerClientManager {  factory LocalServerWebViewManager() => _getInstance();  static LocalServerWebViewManager get instance => _getInstance();  static LocalServerWebViewManager? _instance;  static LocalServerWebViewManager _getInstance() {    _instance ??= LocalServerWebViewManager._internal();    return _instance!;  }  LocalServerWebViewManager._internal();  /// 测试的配置  void initSetting() {    init();    LocalServerCacheBinderSetting.instance.setBaseHost('https://jomin-web.web.app');    Map<String, dynamic> baCache = {'common': {'compress': '/local-server/common.zip', "version": "20220503"}};    LocalServerClientConfig localServerClientConfig = LocalServerClientConfig.fromJson({      'option': [{'key': 'test-one', 'open': 1, 'priority': 0, "version": "20220503"}],      'assets': {        'test-one': {'compress': '/local-server/test-one.zip'}      },      'basics': baCache,    });    prepareManager(localServerClientConfig);    startLocalServer();  }}

    可以写对应的获取配置json的方法,设置上去,然后在需要的时候打开LocalServer。

    展示与分析

    Flutter WebView预加载如何实现

    Android模拟机展示

    分析

    使用我这边的几个实际项目中的webview进行测试,对于越“静态”的页面的优化效果越好,就是说,可被LocalServer实际服务到的资源越多,首次加载的优化效果就越好。

    比如纯静态页面,iOS的加载完成时间,取20次首次加载的平均值,

    • 未开启LocalServer的平均加载时间为343ms

    • 开启LocalServer的平均加载时间为109ms

    (时间由Safari的网页检查器统计)

    非首次则优化相对没有这么明显,因为未开启情况下除了html均会被缓存。

    • 未开启LocalServer的非首次平均加载时间为142ms

    • 开启LocalServer的非首次平均加载时间为109.4ms

    未开启的最快的加载时间还会比开启的快。由html的加载速度决定。

    若是非纯静态页面,开启和未开启的时间都会受到网络状况的影响,开启LocalServer依旧有优化效果,

    Flutter WebView预加载如何实现

    未开启LocalServer

    Flutter WebView预加载如何实现

    开启LocalServer

    但可以看到静态资源的读取速度LocalServer下依旧比较快,而其他的资源则不稳定了。

    总结

    对于打包到资源包中的资源,首次加载LocalServer可以有比较明显的优化效果,且速度比较稳定,不会受到网络波动的影响。

    但是呢,使用了LocalServer,便无法使用浏览器自身的缓存,对于非首次情况优化效果不大。

    并且,LocalServer可能会有更新的问题,何时去检查配置是否有更新?或许可以通过长链下发通知的方式,但没有长链的话就得考虑下其他的方法来解决更新及时性的问题了。

    Demo

    Demo地址:GitHub.com/EchoPuda/lo&hellip;

    是个插件形式,可以直接使用。 有些东西可以根据业务调整,比如新增特殊的配置、资源包是否要分包、LocalServer的服务也可以根据url来开启不同的服务等。

    我是触发预加载后会将下载成功或已经成功的资源保存到内存中,也可以在读取时再进行对应的IO读取文件,速度会相应慢一点。

    读到这里,这篇“Flutter WebView预加载如何实现”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网精选频道。

    --结束END--

    本文标题: Flutter WebView预加载如何实现

    本文链接: https://lsjlt.com/news/329970.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    猜你喜欢
    • Flutter WebView预加载如何实现
      本文小编为大家详细介绍“Flutter WebView预加载如何实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Flutter WebView预加载如何实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来...
      99+
      2023-06-30
    • Flutter WebView 预加载实现方法(Http Server)
      目录背景分析HttpServer接下来?资源配置下载解压与本地存储版本管理与更新获取LocalServer Url并加载Webview兜底措施统一管理展示与分析总结Demo背景 We...
      99+
      2024-04-02
    • webpack如何实现懒加载和预加载
      小编给大家分享一下webpack如何实现懒加载和预加载,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!正常加载为了看的方便,index.js中的代码非常简单console.log('index.js执行了')...
      99+
      2023-06-22
    • 【Flutter】Flutter 如何使用 WebView
      文章目录 一、前言二、WebView 的概念三、Flutter 中的 WebView1. 使用的库2. 方法介绍 四、代码示例1. 简单示例2. 完整示例 五、总结 一、前言 在移动应用开发中,我们有时候需要在应...
      99+
      2023-08-16
      flutter android webview ios 原力计划
    • JS如何实现图片预加载之无序预加载功能
      这篇文章主要介绍JS如何实现图片预加载之无序预加载功能,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!图片预加载之无序预加载的效果图如下所示,如果大家感觉不错,请参考实现代码。具体代码...
      99+
      2024-04-02
    • 正确在Flutter中添加webview实现详解
      目录前言安装运行项目遇到的问题前言 为什么要在flutter中引入webview?这不是废话么,当然是为了加载一个网页,这不是移动端最基本的需求么,哈哈!说的真不错,接下来我要是告诉...
      99+
      2022-12-08
      Flutter添加webview Flutter webview
    • Flutter中使用WebView加载本地Html文件
      当前的WebView插件都是使用HTML的URL方式加载网页。因此我们有时需要把数据保存为文件,再使用WebView去加载本地网页。 流程变更为:网络请求数据 -> 组装成标准的HTML(一般是静态的...
      99+
      2023-09-18
      flutter html android
    • javascript如何实现图片预加载和懒加载功能
      这篇文章主要介绍“javascript如何实现图片预加载和懒加载功能”,在日常操作中,相信很多人在javascript如何实现图片预加载和懒加载功能问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”javascr...
      99+
      2023-06-14
    • 小程序如何实现图片预加载
      这篇文章主要为大家展示了“小程序如何实现图片预加载”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“小程序如何实现图片预加载”这篇文章吧。    网页有页面预加载,小程序也有图片预加载,小程序图片加...
      99+
      2023-06-26
    • javascript实现图片预加载和懒加载
      本文实例为大家分享了javascript实现图片预加载和懒加载的具体代码,供大家参考,具体内容如下 预加载 预加载是预先加载好后面需要用到的资源, 后面使用的时候直接去缓存里取。举个...
      99+
      2024-04-02
    • Android之WebView加载PDF链接预览PDF文件
      文章目录 前言一、效果图二、实现步骤1.在项目main目录下新建一个assets2.新建一个js为index.js3.新建一个HTML为index.html4.xml布局4.Activity类...
      99+
      2023-09-01
      WebView加载PDF Android加载PDF pdf预览 webview加载PDF链接 Android webview
    • CSS3如何实现loading预加载动画特效
      小编给大家分享一下CSS3如何实现loading预加载动画特效,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!该loading特效...
      99+
      2024-04-02
    • 如何使用require.context实现优雅的预加载
      目录前言丑陋的预加载单张预加载多张预加载优雅的预加载require.context前言 在前端开发中,对页面花里胡哨度[注1]要求越高的页面,用到的图片、音频什么的就越多,比如什么结...
      99+
      2023-05-19
      require.context预加载 require.context使用
    • flutter实现倒计时加载页面
      本文实例为大家分享了flutter实现倒计时加载页面的具体代码,供大家参考,具体内容如下 效果图 实现步骤 1、pubspec.yaml中添加依赖 flustars,该包的Time...
      99+
      2024-04-02
    • Android开发如何实现webview中img标签加载本地图片
      这篇文章主要介绍Android开发如何实现webview中img标签加载本地图片,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!具体如下:在网上查了很多教程,感觉很麻烦,各种方法,最后实践很简单,主要是两步:WebSe...
      99+
      2023-05-30
      android webview
    • android使用RxJava实现预加载
      在上一篇文章中介绍了使用非RxJava环境下,使用Handler机制SyncBarrier的特性实现预加载功能的方法。 在RxJava的环境下使用BehaviorSubject...
      99+
      2022-06-06
      rxjava 预加载 Android
    • WordPress怎么实现HTML5预加载
      小编给大家分享一下WordPress怎么实现HTML5预加载,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!  HTM...
      99+
      2024-04-02
    • Flutter webview与网页通讯交互实现
      目录前言预览具体实现flutter中使用ds_bridge网页端使用dsbridge_flutter总结前言 在app开发中我们有JSBridge来实现app和网页端通讯,现参考JS...
      99+
      2024-04-02
    • Flutter中实现交互式Webview的方法
      前言: Flutter是一款强大的跨平台移动应用开发框架,而Webview则是在应用中展示Web内容的重要组件。本文将介绍如何在Flutter应用中实现交互式的Webview,以便为用户提供更加丰...
      99+
      2023-09-10
      flutter
    • uniapp如何实现预加载其他几个TabBar页面
      近年来,移动应用已成为人们生活必不可少的一部分。而随着移动应用的发展,越来越多的应用采用了TabBar设计,特别是在App中,TabBar已经成为许多应用的主要导航方式。其中,Uniapp框架可以说是目前最受欢迎的轻量级跨平台开发框架。然而...
      99+
      2023-05-14
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作