iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >Flutter WebView 预加载实现方法(Http Server)
  • 571
分享到

Flutter WebView 预加载实现方法(Http Server)

2024-04-02 19:04:59 571人浏览 薄情痞子
摘要

目录背景分析httpserver接下来?资源配置下载解压与本地存储版本管理与更新获取LocalServer Url并加载WEBview兜底措施统一管理展示与分析总结Demo背景 We

背景

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(h5Path, 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还在使用时被关闭。

@override
void 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);
// 替换为原始url
nowUri = nowUri.replace(
    scheme: baseUri.scheme, host: baseUri.host, port: baseUri.port);
// dio请求,responseType 必须是bytes
var 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。

展示与分析

Android模拟机展示

分析

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

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

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

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

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

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

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

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

未开启LocalServer

开启LocalServer

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

总结

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

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

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

Demo

Demo地址:GitHub.com/EchoPuda/lo…

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

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

到此这篇关于Flutter WebView 预加载实现 Http Server的方法的文章就介绍到这了,更多相关Flutter WebView 预加载内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Flutter WebView 预加载实现方法(Http Server)

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

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

本篇文章演示代码以及资料文档资料下载

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

下载Word文档
猜你喜欢
  • Flutter WebView 预加载实现方法(Http Server)
    目录背景分析HttpServer接下来?资源配置下载解压与本地存储版本管理与更新获取LocalServer Url并加载Webview兜底措施统一管理展示与分析总结Demo背景 We...
    99+
    2024-04-02
  • Flutter WebView预加载如何实现
    本文小编为大家详细介绍“Flutter WebView预加载如何实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Flutter WebView预加载如何实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来...
    99+
    2023-06-30
  • Flutter中实现交互式Webview的方法
    前言: Flutter是一款强大的跨平台移动应用开发框架,而Webview则是在应用中展示Web内容的重要组件。本文将介绍如何在Flutter应用中实现交互式的Webview,以便为用户提供更加丰...
    99+
    2023-09-10
    flutter
  • PHP7.4中怎么实现预加载方法
    这篇文章将为大家详细讲解有关PHP7.4中怎么实现预加载方法,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。前言PHP 7.4增加了预加载支持,这一功能可以显...
    99+
    2024-04-02
  • 正确在Flutter中添加webview实现详解
    目录前言安装运行项目遇到的问题前言 为什么要在flutter中引入webview?这不是废话么,当然是为了加载一个网页,这不是移动端最基本的需求么,哈哈!说的真不错,接下来我要是告诉...
    99+
    2022-12-08
    Flutter添加webview Flutter webview
  • Angular实现预加载延迟模块的方法
    Angular实现预加载延迟模块的方法?这个问题可能是我们日常学习或工作经常见到的。希望通过这个问题能让你收获颇深。下面是小编给大家带来的参考内容,让我们一起来看看吧!在使用路由延迟加载中,我们介绍了如何使...
    99+
    2024-04-02
  • javascript实现图片预加载和懒加载
    本文实例为大家分享了javascript实现图片预加载和懒加载的具体代码,供大家参考,具体内容如下 预加载 预加载是预先加载好后面需要用到的资源, 后面使用的时候直接去缓存里取。举个...
    99+
    2024-04-02
  • webpack如何实现懒加载和预加载
    小编给大家分享一下webpack如何实现懒加载和预加载,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!正常加载为了看的方便,index.js中的代码非常简单console.log('index.js执行了')...
    99+
    2023-06-22
  • flutter实现倒计时加载页面
    本文实例为大家分享了flutter实现倒计时加载页面的具体代码,供大家参考,具体内容如下 效果图 实现步骤 1、pubspec.yaml中添加依赖 flustars,该包的Time...
    99+
    2024-04-02
  • JS如何实现图片预加载之无序预加载功能
    这篇文章主要介绍JS如何实现图片预加载之无序预加载功能,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!图片预加载之无序预加载的效果图如下所示,如果大家感觉不错,请参考实现代码。具体代码...
    99+
    2024-04-02
  • WordPress怎么实现HTML5预加载
    小编给大家分享一下WordPress怎么实现HTML5预加载,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!  HTM...
    99+
    2024-04-02
  • JavaScript懒加载与预加载原理与实现详解
    目录1、懒加载1.1、什么是懒加载1.2、为什么要使用懒加载1.3、懒加载的优点1.4、懒加载的原理1.5、懒加载的实现步骤1.6、懒加载的实现方式2、预加载2.1、什么是预加载2....
    99+
    2024-04-02
  • Android webview实现拍照的方法
    Android webview实现拍照的方法 html <div id="pnlVideo1"> <input type="hidden" name="imgNric1" id="im...
    99+
    2023-05-30
    android webview 拍照
  • android预加载效果怎么实现
    实现Android预加载效果可以使用以下几种方法:1. 使用AsyncTask:在Activity或Fragment中创建一个Asy...
    99+
    2023-08-17
    android
  • c语言实现http下载器的方法
    一、介绍 最近做ota升级需要用到http下载,所以写了一下http下载器 实现流程 1、解析url网址的域名和文件名 2、获取ip地址 3、构建http请求头发送到服务器 4、解...
    99+
    2024-04-02
  • Springbean加载控制实现方法
    目录1. Controller加载控制1.1 Controller加载控制与业务bean加载控制1.2 加载Spring控制的bean的时候排除掉SpringMVC控制的bean(方...
    99+
    2022-12-23
    Spring bean加载控制 bean加载控制
  • flutter怎么实现倒计时加载页面
    本篇内容主要讲解“flutter怎么实现倒计时加载页面”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“flutter怎么实现倒计时加载页面”吧!效果图实现步骤pubspec.yaml中添加依赖 f...
    99+
    2023-06-29
  • javascript如何实现图片预加载和懒加载功能
    这篇文章主要介绍“javascript如何实现图片预加载和懒加载功能”,在日常操作中,相信很多人在javascript如何实现图片预加载和懒加载功能问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”javascr...
    99+
    2023-06-14
  • Mybatis加载策略的实现方法
    目录MaBatis加载策略1.什么是延迟加载1.2实现1.2.1局部延迟加载1.2.2设置触发延迟加载的方法1.2.3全局延迟加载MaBatis加载策略 1.什么是延迟加载 Myba...
    99+
    2024-04-02
  • Oracle数据加载和卸载的实现方法
    在日常工作中;经常会遇到这样的需求: Oracle 数据表跟文本或者文件格式进行交互;即将指定文件内容导入对应的 Oracle 数据表中;或者从 Oracle 数据表导出。 其他数据库中的表跟Or...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作