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

Flutter?WebView性能優(yōu)化使h5像原生頁面一樣優(yōu)秀

 更新時間:2023年02月13日 14:12:36   作者:IAM17  
這篇文章主要為大家介紹了Flutter?WebView性能優(yōu)化使h5像原生頁面一樣優(yōu)秀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

WebView 的文章分兩篇

本篇和大家一起討論下性能優(yōu)化的問題。

WebView 頁面的體驗上之所以不如原生頁面,主要是因為原生頁面可以馬上顯示出頁面骨架,一下子就能看到內(nèi)容。WebView 需要先根據(jù) url 去加載 html,加載到 html 后才能加載 css ,css 加載完成后才能正常顯示頁面內(nèi)容,至少多出兩步網(wǎng)絡請求。有的頁面是用 js 渲染的,這樣時間會更長。要想讓 WebView 頁面能接近 Flutter 頁面的體驗,主要就是要省掉網(wǎng)絡請求的時間。

做優(yōu)化要考慮到很多方面,在成本與收益之間做平衡。如果不是新開項目,需要考慮項目當前的情況。下面分兩種情況討論一下。

服務端渲染

頁面 html 已經(jīng)在服務端拼接完成。只需要 html,css 就可以正常查看頁面(主要內(nèi)容不受影響)。如果你的項目的頁面是這樣的,那么我們已經(jīng)有了一個好的起點。

WebView 要顯示一個頁面,需要串行下面的過程。通過 url 加載到 html 后再加載 css,css 加載完成后顯示頁面。

url -> html -> css -> 顯示

我們可以對 css 的請求做一下優(yōu)化。優(yōu)化方案有兩種

  • 內(nèi)聯(lián) css 到 html
  • 把 css 緩存到本地。

第一種方案比較容易做,修改一下頁面的打包方案即可。很容易實現(xiàn)一份代碼打包出兩個頁面,一個外鏈 css ,一個內(nèi)聯(lián)css。但壞處也是很明顯的,每次都加載同樣的 css,會增加網(wǎng)絡傳輸,如果網(wǎng)絡不佳的話,對首屏時間可能會產(chǎn)生明顯的影響。就算拋開首屏時間,也會對用戶的流量造成浪費。

第二種方案可以解決 css 重復打包的問題。首先要考慮的問題是:css 放在本地的哪個地方?

css 放哪里

有兩個地方可以放

  • 放在 asset,和 app 一起打包發(fā)布,好處是簡單可靠,壞處是不方便更新。
  • 放在 文檔目錄,好處是可以隨時更新,壞處是邏輯上會復雜一些。

文檔目錄用于存儲只能由該應用訪問的文件,系統(tǒng)不會清除該目錄,只有在刪除應用時才會消失。

從技術(shù)上來說,這兩種方案都是可以的。先說下不方便更新的問題:既然 app 的其它頁面都不能隨便更新,為什么不能接受這個頁面的樣式不能隨便更新?如果是害怕版本沖突,那也好解決,發(fā)一次版,更新一次頁面地址,每個版本都有其對應的頁面地址,這樣就不會沖突了。根本原因是掌控的誘因,即使你能控制住誘因,你的老板也控制不住。所以還是老老實實選第二種方案吧。

放哪里的問題解決了,接下來要考慮的是如何更新 css 的問題。

更新 css

因為有可能 app 啟動后第一個展示的就是這個頁面,所以要在 app 啟動后第一時間就更新 css。但又有一個問題,每次啟動都更新同樣的內(nèi)容是在浪費流量。解決辦法是加一個配置,每次啟動后第一時間加載這個配置,通過配置信息來判斷要不要更新 css。

這個配置一定要很小,比如可以用二進制 01 表示true false,當然了可能不需要這么極端,用一個 map 就好。

如何利用本地 css 快速顯示頁面

在 app 上啟動一個本地 http server 提供 css。 我們可以在打包的時候把 css 的外鏈寫成本地 http,比如 http://localhost:8080/index.css

除了 css,頁面的重要圖片,字體等靜態(tài)資源也可以放在本地,只要加載到 html 就可以立即顯示頁面,省了一步需要串行的網(wǎng)絡請求。

到這里服務端渲染頁面的優(yōu)化就完成了,還是很簡單的吧,示例代碼在后面。

瀏覽器渲染

近年來,隨著 vue,react 的興起,由 js 在瀏覽器中拼接 html 逐漸成為主流。雖然可以用同構(gòu)的方案,但那樣會增加成本,除非必須,一般都是只在瀏覽器渲染??赡苣愕捻撁嬲沁@樣的。我們來分析一下。

WebView 要顯示一個頁面,需要串行下面的過程。通過 url 加載到 html 后再加載 css、js,js 請求完數(shù)據(jù)后才能顯示頁面。

url -> html -> css,js -> js 去加載數(shù)據(jù) -> 顯示

和服務端渲染的頁面相比,首次請求時間更長。多出了 js 加載數(shù)據(jù)的時間。除了要緩存 css,還要緩存 js 和數(shù)據(jù)。緩存 js 是必須的,緩存數(shù)據(jù)是可選的。好消息是 html 只有骨架,沒有內(nèi)容,可以連 html 也一起緩存。

緩存 js,html 的方案和緩存 css 的方案是一樣的。緩存數(shù)據(jù)會面臨數(shù)據(jù)更新的難題,所以只可以緩存少量不需要時時更新的少量重要數(shù)據(jù),不需要所有數(shù)據(jù)都緩存。app 的原生頁面也是需要加載數(shù)據(jù)的,也不是每種數(shù)據(jù)都要緩存。

數(shù)據(jù)更新之所以說是一個難題,是因為很多內(nèi)容數(shù)據(jù)是需要即時更新的。但數(shù)據(jù)已經(jīng)下發(fā)到客戶端,已經(jīng)緩存起來,客戶端不再發(fā)起新的請求,如何通知客戶端進行數(shù)據(jù)更新?雖然有輪詢,socket,服務端推送等方案可以嘗試,但開發(fā)成本都比較高,和獲得的收益相比,代價太大。

當緩存了 html,css,js 等靜態(tài)資源后,h5 就已經(jīng)和原生頁面站在同一起跑線上了,對于只讀的頁面,體驗上相差無幾。

加載數(shù)據(jù)后還有js 拼接 html 的時間,和加載的時間相比,只要硬件還可以的情況下,消耗的時間可以忽略

圖片不適合用緩存 css 的方案,因為圖片太大也太多。只能預加載少量最重要的圖片,其它大量圖片只能對二次加載做優(yōu)化,我們會在后面討論

瀏覽器渲染的頁面也需要打包的配合,需要把所有的要緩存的靜態(tài)資源地址都換成本地地址,這就要求發(fā)布的時候一份代碼需要發(fā)布兩個頁面。一個是給瀏覽器用的,資源都通過網(wǎng)絡加載。一個是給 WebView 用的,資源都從本地獲取。

思路已經(jīng)有了,具體實現(xiàn)就簡單了。下面我給出關鍵環(huán)節(jié)的示例代碼,供大家參考。

如何啟動本地server

本地不需要 https,用 http 用行了,但是需要在 AndroidManifest.xml 的 applictation 中做如下配置 android:usesCleartextTraffic="true"

import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_static/shelf_static.dart';
import 'package:path_provider/path_provider.dart';
Future<void> initServer(webRoot) async {
  var documentDirectory = await getApplicationDocumentsDirectory();
  var handler =
      createStaticHandler('${documentDirectory.path}/$webRoot', defaultDocument: 'index.html');
  io.serve(handler, 'localhost', 8080);
}

createStaticHandler 負責處理靜態(tài)資源。

如果要兼容 windows 系統(tǒng),路徑需要用 path 插件的 join 方法拼接

如何讓 WebView 的頁面請求走本地服務

兩種方案:

  • 打包的時候需要緩存的頁面的地址都改成本地地址
  • 對頁面請求 在 WebView 中進行攔截,讓已經(jīng)緩存的頁面走本地 server。

相比之下,第 2 種方案都好一些??梢酝ㄟ^配置文件靈活修改哪些頁面需要緩存。

在下面的示例代碼中 ,cachedPagePaths 存儲著需要緩存的頁面的 path。

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class MyWebView extends StatefulWidget {
  const MyWebView({super.key, required this.url, this.cachedPagePaths = const []});
  final String url;
  final List<String> cachedPagePaths;
  @override
  State<MyWebView> createState() => _MyWebViewState();
}
class _MyWebViewState extends State<MyWebView> {
  late final WebViewController controller;
  @override
  void initState() {
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(NavigationDelegate(
        onNavigationRequest: (request) async {
          var uri = Uri.parse(request.url);
          // TODO: 還應該判斷下 host
          if (widget.cachedPagePaths.contains(uri.path)) {
            var url = 'http://localhost:8080/${uri.path}';
            Future.microtask(() {
              controller.loadRequest(Uri.parse(url));
            });
            return NavigationDecision.prevent;
          } else {
            return NavigationDecision.navigate;
          }
        },
      ))
      ..loadRequest(Uri.parse(widget.url));
    super.initState();
  }
  @override
  void didUpdateWidget(covariant MyWebView oldWidget) {
    if(oldWidget.url!=widget.url){
      controller.loadRequest(Uri.parse(widget.url));
    }
    super.didUpdateWidget(oldWidget);
  }
  @override
  Widget build(BuildContext context) { 
    return Column(
      children: [Expanded(child: WebViewWidget(controller: controller))],
    );
  }
}

優(yōu)化圖片請求

如果頁面中有很多圖片,你會發(fā)現(xiàn),體驗上還是不如 Flutter 頁面,為什么呢?原來 Flutter Image Widget 使用了緩存,把請求到的圖片都緩存了起來。 要達到相同的體驗,h5 頁面也需要實現(xiàn)相同的緩存功能。

關于 Flutter 圖片請參見 快速掌握 Flutter 圖片開發(fā)核心技能

代碼實現(xiàn)

要如何實現(xiàn)呢?只需要兩步。

  • 打包的時候需要把圖片的外鏈請求改成本地請求
  • 本地 server 對圖片請求進行攔截,優(yōu)先讀緩存,沒有再去請求網(wǎng)絡。

第 1 條我舉個例子,比如圖片的地址為 https://juejin.com/logo.png ,打包的時候需要修改為 http://localhost:8080/logo.png

第 2 條的實現(xiàn)上,我們?nèi)€巧,借用 Flutter 中的 NetworkImage,NetworkImage 有緩存的功能。

下面給出完整示例代碼,貼到 main.dart 中就能運行。運行代碼后看到一段文字和一張圖片。

注意先安裝相關的插件,插件的名字 import 里有。

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:typed_data';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_static/shelf_static.dart';
import 'dart:ui' as ui;
import 'package:webview_flutter/webview_flutter.dart';
const htmlString = '''
<!DOCTYPE html>
<head>
<title>webview demo | IAM17</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, 
  maximum-scale=1.0, user-scalable=no,viewport-fit=cover" />
<style>
*{
  margin:0;
  padding:0;
}
body{
   background:#BBDFFC;  
   text-align:center;
   color:#C45F84;
   font-size:20px;
}
img{width:90%;}
p{margin:30px 0;}
</style>
</head>
<html>
<body>
<p>大家好,我是 17</p>
<img src='http://localhost:8080/tos-cn-i-k3u1fbpfcp/
c6208b50f419481283fcca8c44a2e3af~tplv-k3u1fbpfcp-watermark.image'/>
</body>
</html>
''';
void main() async {
  runApp(const MyApp());
}
class MyApp extends StatefulWidget {
  const MyApp({super.key});
  @override
  State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  WebViewController? controller;
  @override
  void initState() {
    init();
    super.initState();
  }
  init() async {
    var server = Server17(remoteHost: 'p6-juejin.byteimg.com');
    await server.init();
    var filePath = '${server.webRoot}/index.html';
    var indexFile = File(filePath);
    await indexFile.writeAsString(htmlString);
    setState(() {
      controller = WebViewController()
        ..loadRequest(Uri.parse('http://localhost:${server.port}/index.html'));
    });
  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      body: SafeArea(
        child: controller == null
            ? Container()
            : WebViewWidget(controller: controller!),
      ),
    ));
  }
}
class Server17 {
  Server17(
      {this.remoteSchema = 'https',
      required this.remoteHost,
      this.port = 8080,
      this.webFolder = 'www'});
  final String remoteSchema;
  final String remoteHost;
  final int port;
  final String webFolder;
  String? _webRoot;
  String get webRoot {
    if (_webRoot == null) throw Exception('請在初始化后讀取');
    return _webRoot!;
  }
  init() async {
    var documentDirectory = await getApplicationDocumentsDirectory();
    _webRoot = '${documentDirectory.path}/$webFolder';
    await _createDir(_webRoot!);
    var handler = Cascade()
        .add(getImageHandler)
        .add(createStaticHandler(_webRoot!, defaultDocument: 'index.html'))
        .handler;
    io.serve(handler, InternetAddress.loopbackIPv4, port);
  }
  _createDir(String path) async {
    var dir = Directory(path);
    var exist = dir.existsSync();
    if (exist) {
      return;
    }
    await dir.create();
  }
  Future<Uint8List?> loadImage(String url) async {
    Completer<ui.Image> completer = Completer<ui.Image>();
    ImageStreamListener? listener;
    ImageStream stream = NetworkImage(url).resolve(ImageConfiguration.empty);
    listener = ImageStreamListener((ImageInfo frame, bool sync) {
      final ui.Image image = frame.image;
      completer.complete(image);
      if (listener != null) {
        stream.removeListener(listener);
      }
    });
    stream.addListener(listener);
    var uiImage = await completer.future;
    var pngBytes = await uiImage.toByteData(format: ui.ImageByteFormat.png);
    if (pngBytes != null) {
      return pngBytes.buffer.asUint8List();
    }
    return null;
  }
  FutureOr<Response> getImageHandler(Request request) async {
    if (RegExp(
      r'\.(png|image)$',
    ).hasMatch(request.url.path)) {
      var url = '$remoteSchema://$remoteHost/${request.url.path}';
      var imageData = await loadImage(url);
      //TODO: 如果 imageData 為空,改成錯誤圖片
      return Response.ok(imageData);
    } else {
      return Response.notFound('next');
    }
  }
}

代碼邏輯

  • 在本地文檔目錄的 www 文件夾中準備了一個 index.html 文件
  • 啟動本地 server,通過訪問 http://localhost:8080/index.html 請求本地頁面。
  • server 收到請求后,對圖片請求進行攔截,通過 NetworkImage 返回圖片。

第 2 條。本例中是直接訪問的 localhost,實際應用中,頁面地址是外鏈地址,通過攔截的方式請求本地。如何做頁面地址攔截前面已經(jīng)給出示例了。

第 3 條。打包后的時候?qū)λ袌D片地址都寫成了本地地址,改成本地地址的目的就是為了讓圖片請求都由本地 server 響應。本地 server 拿到 圖片地址后,再改回網(wǎng)絡地址,通過 NetworkImage 請求圖片。NetworkImage 會首先判斷有沒有緩存,有直接用,沒有就發(fā)起網(wǎng)絡請求,然后再緩存。

可能你覺得有點繞,既然最后還要用網(wǎng)絡地址,為什么還要先寫成本地地址,象攔截頁面請求那樣攔截圖片請求不香嗎?答案是不可以。兩個原因。

  • webview_flutter 只能攔截頁面請求。
  • 本地 server 不方便攔截 443 端口。

對比于攔截 443 端口,修改打包方案要容易的多。

關于圖片類型

在示例代碼中,用 RegExp( r'\.(png|image)$',) 判斷是否要響應請求。從正則可以看出,以 png 或 image 結(jié)果的圖片都能響應請求。判斷 image 是因為示例中的圖片地址是以 image 結(jié)尾的。

示例代碼只能支持 png 格式的圖片,示例圖片雖然是 image 結(jié)尾,但格式也是 png 格式。如果要支持更多格式的圖片,需要用到第三方庫。

關于圖片地址

如果圖片地址失改,可以自行換一個,隨使在網(wǎng)上找個 png 圖片 地址就行。

把圖片緩存到磁盤。

我們演示了把圖片緩存到內(nèi)存,當 app 被殺掉,緩存都沒了,除非緩存到磁盤。這項工作已經(jīng)有插件幫我們做了。 用 cached_network_image 替換 NetworkImage,稍加改動就可以實現(xiàn)磁盤緩存了。

總結(jié)一下

服務端染頁面方案

  • 打包的時候需要打出兩個頁面,一個頁面的 css 外鏈接是外網(wǎng),一個頁面的 css 鏈接是本地。
  • 在 App 啟動的時候根據(jù)配置信息預加載 css 存到文檔目錄。
  • 啟動本地 server 響應 css 的請求。

瀏覽器渲染方案

  • 打包的時候需要打出兩個頁面,一個頁面的 css,js 鏈接是外網(wǎng),一個頁面的 css,js 鏈接是本地。
  • 在 App 啟動的時候根據(jù)配置信息預加載 html,css,js 存到文檔目錄。
  • 根據(jù)配置信息攔截頁面請求,已經(jīng)緩存的頁面改走本地 server。
  • 啟動本地 server 響應 html,css,js 的請求

圖片緩存

如果不做圖片緩存,通過前面兩個方案,h5 速度就已經(jīng)得到大大提高了。如果有余力,可以做圖片緩存。圖片緩存是可選的,是對前面兩種方案的加強。

  • 給 app 用的頁面打包的時候把圖片地址換成本地地址。
  • 啟動本地 server 響應圖片請求,有緩存就讀緩存,沒有緩存走網(wǎng)絡。

可能你的項目不同,有不同的方案,歡迎一起討論。

本文到這里就結(jié)束了,謝謝觀看。

番外

為了給自己一點壓力,上一篇 在 Flutter 中使用 webview_flutter 4.0 | js 交互 中我就預告說今天要發(fā)這篇性能優(yōu)化的文章。結(jié)果壓力是有的了,但卻沒能按時完工(理想情況是周日下午完工,這樣可以休息一下)。一個原因是 升級 flutter 報錯,浪費了一個上午,再有就是寫了一版后,并不滿意,又重寫了一版,最后才定稿。一直寫到深夜才把主要內(nèi)容寫完。早上起來又做了補充修改。

以上就是Flutter WebView性能優(yōu)化使h5像原生頁面一樣優(yōu)秀的詳細內(nèi)容,更多關于Flutter WebView頁面優(yōu)化的資料請關注腳本之家其它相關文章!

相關文章

最新評論