中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Flutter?WebView預加載如何實現

發布時間:2022-05-23 13:42:23 來源:億速云 閱讀:588 作者:iii 欄目:開發技術

本文小編為大家詳細介紹“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還在使用時被關閉。

    @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。

    展示與分析

    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預加載如何實現”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    桂林市| 新乐市| 张家界市| 吉安县| 临桂县| 米林县| 定结县| 出国| 祥云县| 礼泉县| 商洛市| 威海市| 化州市| 芜湖县| 英吉沙县| 永福县| 阿拉善右旗| 普兰店市| 萨迦县| 白城市| 天津市| 章丘市| 南木林县| 潢川县| 玉门市| 建瓯市| 金堂县| 峨边| 尚义县| 金溪县| 安龙县| 白水县| 江北区| 博兴县| 南康市| 新密市| 泰顺县| 金华市| 宜兰市| 德昌县| 南召县|