我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Flutter WebView预加载如何实现

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Flutter WebView预加载如何实现

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

    背景

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

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

    分析

    在原生Android和iOS中,有一种预缓存资源,并在加载时拦截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预加载如何实现”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    Flutter WebView预加载如何实现

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    Flutter WebView预加载如何实现

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

    webpack如何实现懒加载和预加载

    小编给大家分享一下webpack如何实现懒加载和预加载,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!正常加载为了看的方便,index.js中的代码非常简单console.log(index.js执行了)import { t
    2023-06-22

    正确在Flutter中添加webview实现详解

    这篇文章主要为大家介绍了正确在Flutter中添加webview实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-08

    javascript如何实现图片预加载和懒加载功能

    这篇文章主要介绍“javascript如何实现图片预加载和懒加载功能”,在日常操作中,相信很多人在javascript如何实现图片预加载和懒加载功能问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”javascr
    2023-06-14

    小程序如何实现图片预加载

    这篇文章主要为大家展示了“小程序如何实现图片预加载”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“小程序如何实现图片预加载”这篇文章吧。    网页有页面预加载,小程序也有图片预加载,小程序图片加
    2023-06-26

    如何使用require.context实现优雅的预加载

    这篇文章主要介绍了使用require.context实现优雅的预加载 ,需要的朋友可以参考下
    2023-05-19

    Android开发如何实现webview中img标签加载本地图片

    这篇文章主要介绍Android开发如何实现webview中img标签加载本地图片,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!具体如下:在网上查了很多教程,感觉很麻烦,各种方法,最后实践很简单,主要是两步:WebSe
    2023-05-30

    android使用RxJava实现预加载

    在上一篇文章中介绍了使用非RxJava环境下,使用Handler机制SyncBarrier的特性实现预加载功能的方法。 在RxJava的环境下使用BehaviorSubject的特性来实现也是很方便的。BehaviorSubject内部会缓
    2022-06-06

    uniapp如何实现预加载其他几个TabBar页面

    近年来,移动应用已成为人们生活必不可少的一部分。而随着移动应用的发展,越来越多的应用采用了TabBar设计,特别是在App中,TabBar已经成为许多应用的主要导航方式。其中,Uniapp框架可以说是目前最受欢迎的轻量级跨平台开发框架。然而,许多开发者在使用Uniapp开发TabBar应用时,都会遇到一个共同的问题:如何实现预加载其他几个TabBar页面?在Uniapp开发过程
    2023-05-14

    flutter怎么实现倒计时加载页面

    本篇内容主要讲解“flutter怎么实现倒计时加载页面”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“flutter怎么实现倒计时加载页面”吧!效果图实现步骤1、pubspec.yaml中添加依赖
    2023-06-29

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录