欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Flutter?WebView?預(yù)加載實(shí)現(xiàn)方法(Http?Server)

 更新時(shí)間:2022年05月23日 11:38:42   作者:BlackDream  
這篇文章主要介紹了Flutter?WebView?預(yù)加載實(shí)現(xiàn)方法,包括資源的配置,資源的下載和存儲(chǔ),版本的管理,如何根據(jù)實(shí)際url獲取對(duì)應(yīng)HttpServer?bind的url等,需要的朋友可以參考下

背景

WebView是在APP中,可以很方便的展示web頁面,并且與web交互APP的數(shù)據(jù)。方便,并且更新內(nèi)容無需APP發(fā)布新版本,只需要將最新的web代碼部署完成,用戶重新刷新即可。

在WebView中,經(jīng)常能夠聽到的一個(gè)需求就是:減少首次白屏?xí)r間,加快加載速度。因?yàn)榧虞dweb頁面,必然會(huì)受到網(wǎng)絡(luò)狀況等的影響,無法像原生內(nèi)容一樣把靜態(tài)內(nèi)容秒加載出來。

分析

在原生Android和iOS中,有一種預(yù)緩存資源,并在加載時(shí)攔截web請(qǐng)求,將事先緩存好的資源替換上去,從而實(shí)現(xiàn)預(yù)加載的方案。

  • iOS常見的攔截的框架是CocoaHTTPServer / Telegraph
  • Android則是在WebViewClientshouldInterceptRequest去進(jìn)行攔截

道理都是一樣的。

那么,F(xiàn)lutter有沒有類似的方式去實(shí)現(xiàn)預(yù)加載web資源呢?

有!類似iOS中的CocoaHTTPServer,flutter也有一個(gè)HttpServer,可以發(fā)現(xiàn),他們基本是一樣的功能,并且Flutter HttpServer支持Android和iOS。

HttpServer

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

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.4

權(quán)限要求

因?yàn)橐猦ttp服務(wù),所以需要配置一下允許各平臺(tái)的http請(qǐng)求。

啟動(dòng)服務(wù)

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

HttpServer.bind方法會(huì)開啟偵聽對(duì)應(yīng)Address的請(qǐng)求,第一個(gè)入?yún)ddress可以自定,第二個(gè)port可以為0,也可以自定,為0的話,則由系統(tǒng)隨機(jī)分配一個(gè)臨時(shí)端口

異步返回一個(gè)HttpServer,可以拿到最終的地址,也可以配置一些屬性

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

并且,可以設(shè)置攔截偵聽!

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

listen即常見的StreamSubscription,關(guān)閉時(shí)需要Cancel。 在listen的onDate中,會(huì)提供一個(gè)HttpRequest,即被攔截的請(qǐng)求的HttpRequest。

_responseWebViewReq(HttpRequest request)

我們可以取得其當(dāng)前請(qǐng)求的Uri,并且可以根據(jù)不同的Uri,返回不同的結(jié)果給到該請(qǐng)求的response

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

也可以設(shè)置headers

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

finally,在所有請(qǐng)求結(jié)束時(shí),關(guān)閉該response

request.response.close();

至此,HttpServer攔截的功能就實(shí)現(xiàn)了。

接下來?

當(dāng)然僅僅實(shí)現(xiàn)HttpServer攔截是不夠的,既然我們要實(shí)現(xiàn)預(yù)加載,最主要的攔截方案已經(jīng)有了,那么,接下來就需要考慮,資源的配置,資源的下載和存儲(chǔ),版本的管理,如何根據(jù)實(shí)際url獲取對(duì)應(yīng)HttpServer bind的url等。不在意的話也可以直接跳到最后看Demo。

PS:因?yàn)轫?xiàng)目中命名為L(zhǎng)ocalServerWebview,所以后面代碼中可能稱其為L(zhǎng)ocalServer。

資源配置

我們需要知道,哪些資源是需要被下載的,被使用在LocalServer服務(wù)中的。所以我設(shè)計(jì)了一個(gè)json配置文件,存儲(chǔ)在服務(wù)端中,每次打開App時(shí)下發(fā)。大致的格式為:

{
    "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
}

主要根據(jù)我這邊的web項(xiàng)目配置,option為配置的對(duì)應(yīng)webPath的開關(guān)下載優(yōu)先級(jí)、版本號(hào)

assets中則是option對(duì)應(yīng)的key的壓縮包地址(也可以一起寫在option中,不過實(shí)際業(yè)務(wù)中還有別的配置,所以就這樣吧)

basics則是統(tǒng)一資源的配置,比如common,所有web通用的js、json資源等,便統(tǒng)一下載,避免重復(fù)。

local_server_open是總開關(guān),關(guān)閉時(shí)則LocalServer服務(wù)不會(huì)使用。

然后便是獲取到配置后,對(duì)符合條件的資源進(jìn)行下載解壓和存儲(chǔ)。

// 觸發(fā)basics預(yù)下載
LocalServerDownloadService.instance.preloadBasicsData(json['basics'], basics, oldBasic);
// 觸發(fā)assets預(yù)下載
LocalServerDownloadService.instance.preloadAssetsData(_diffAssets(value, assets));

下載解壓與本地存儲(chǔ)

這邊使用的Dio進(jìn)行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包進(jìn)行解壓

// 找到對(duì)應(yīng)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文件在解壓完成后會(huì)被清理,根據(jù)zipUrl來決定存儲(chǔ)的文件路徑。 若已經(jīng)存在資源,則無需下載。

若是下載失敗的話,會(huì)被標(biāo)記為failure,在重啟app后的新下載任務(wù)中會(huì)重新嘗試。 也可以加個(gè)重試幾次的邏輯。

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

版本管理與更新

在配置json中可以看到version相關(guān)的設(shè)置,在上一步的下載解壓完成之后,會(huì)把文件狀態(tài)、對(duì)應(yīng)的option、assets、basics數(shù)據(jù)(版本)存儲(chǔ)起來。

首先檢查對(duì)應(yīng)的版本號(hào)是否能對(duì)上,若對(duì)不上的話,舊的數(shù)據(jù)將不會(huì)用來去重,而是直接使用最新獲取到的配置進(jìn)行下載和覆蓋。

// 處理 assets 資源,和版本控制
LocalServerConfigCache.getOptions().then((oldOptions) {
  // assets 緩存和版本處理
  LocalServerConfigCache.getAssets().then((value) {
    var oldAssets = value;
    // 版本不對(duì),則移除,并需要下載
    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);
        }
      }
    }
    // 觸發(fā)預(yù)下載
    LocalServerDownloadService.instance.preloadAssetsData(_diffAssets(value, assets));
  **});**
});

在預(yù)下載加入下載隊(duì)列前,會(huì)檢查之前存儲(chǔ)的文件狀態(tài),若是suceess,則跳過不進(jìn)行下載。

_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服務(wù),并且可以根據(jù)不同的url獲取得到對(duì)應(yīng)的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';
}

其實(shí)就是在bind成功之后,將addressport存儲(chǔ)下來,并在獲取的時(shí)候?qū)?strong>query與其拼接。

然后將處理后的url給到webview進(jìn)行加載,即會(huì)觸發(fā)

這里有個(gè)處理是將basics統(tǒng)一資源的鏈接,動(dòng)態(tài)的添加到每個(gè)web頁面的資源列表里。Binder在初始化配置和資源下載完成后,會(huì)存儲(chǔ)ConfigbasicCache到內(nèi)存中。并且統(tǒng)記webpage打開數(shù)量,避免HttpServer還在使用時(shí)被關(guān)閉。

@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,
  ···
)

兜底措施

會(huì)存在些情況就是,預(yù)加載的資源還沒有下載解壓完成或者說資源下載失敗了,用戶就開啟了Webview,這時(shí)候我們就需要用源鏈接(baseDomain)去實(shí)時(shí)獲取到數(shù)據(jù)來替換,避免web頁面異常。

// 找不到本地文件,使用網(wǎng)絡(luò)下載拿到原始數(shù)據(jù)
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請(qǐng)求,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;

統(tǒng)一管理

最終所有的模塊由一個(gè)manager進(jìn)行統(tǒng)一管理,繼承LocalServerClientManger,設(shè)置相應(yīng)的初始化和配置即可。

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();
  /// 測(cè)試的配置
  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();
  }
}

可以寫對(duì)應(yīng)的獲取配置json的方法,設(shè)置上去,然后在需要的時(shí)候打開LocalServer。

展示與分析

Android模擬機(jī)展示

分析

使用我這邊的幾個(gè)實(shí)際項(xiàng)目中的webview進(jìn)行測(cè)試,對(duì)于越“靜態(tài)”的頁面的優(yōu)化效果越好,就是說,可被LocalServer實(shí)際服務(wù)到的資源越多,首次加載的優(yōu)化效果就越好。

比如純靜態(tài)頁面,iOS的加載完成時(shí)間,取20次首次加載的平均值,

  • 未開啟LocalServer的平均加載時(shí)間為343ms
  • 開啟LocalServer的平均加載時(shí)間為109ms

(時(shí)間由Safari的網(wǎng)頁檢查器統(tǒng)計(jì))

非首次則優(yōu)化相對(duì)沒有這么明顯,因?yàn)槲撮_啟情況下除了html均會(huì)被緩存。

  • 未開啟LocalServer的非首次平均加載時(shí)間為142ms
  • 開啟LocalServer的非首次平均加載時(shí)間為109.4ms

未開啟的最快的加載時(shí)間還會(huì)比開啟的快。由html的加載速度決定。

若是非純靜態(tài)頁面,開啟和未開啟的時(shí)間都會(huì)受到網(wǎng)絡(luò)狀況的影響,開啟LocalServer依舊有優(yōu)化效果,

未開啟LocalServer

開啟LocalServer

但可以看到靜態(tài)資源的讀取速度LocalServer下依舊比較快,而其他的資源則不穩(wěn)定了。

總結(jié)

對(duì)于打包到資源包中的資源,首次加載LocalServer可以有比較明顯的優(yōu)化效果,且速度比較穩(wěn)定,不會(huì)受到網(wǎng)絡(luò)波動(dòng)的影響。

但是呢,使用了LocalServer,便無法使用瀏覽器自身的緩存,對(duì)于非首次情況優(yōu)化效果不大。

并且,LocalServer可能會(huì)有更新的問題,何時(shí)去檢查配置是否有更新?或許可以通過長(zhǎng)鏈下發(fā)通知的方式,但沒有長(zhǎng)鏈的話就得考慮下其他的方法來解決更新及時(shí)性的問題了。

Demo

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

是個(gè)插件形式,可以直接使用。 有些東西可以根據(jù)業(yè)務(wù)調(diào)整,比如新增特殊的配置、資源包是否要分包、LocalServer的服務(wù)也可以根據(jù)url來開啟不同的服務(wù)等。

我是觸發(fā)預(yù)加載后會(huì)將下載成功或已經(jīng)成功的資源保存到內(nèi)存中,也可以在讀取時(shí)再進(jìn)行對(duì)應(yīng)的IO讀取文件,速度會(huì)相應(yīng)慢一點(diǎn)。

到此這篇關(guān)于Flutter WebView 預(yù)加載實(shí)現(xiàn) Http Server的方法的文章就介紹到這了,更多相關(guān)Flutter WebView 預(yù)加載內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論