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

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

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

引言

WebView 的文章分兩篇

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

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

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

服務(wù)端渲染

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

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

url -> html -> css -> 顯示

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

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

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

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

css 放哪里

有兩個(gè)地方可以放

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

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

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

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

更新 css

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

這個(gè)配置一定要很小,比如可以用二進(jìn)制 01 表示true false,當(dāng)然了可能不需要這么極端,用一個(gè) map 就好。

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

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

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

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

瀏覽器渲染

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

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

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

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

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

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

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

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

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

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

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

如何啟動(dòng)本地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 負(fù)責(zé)處理靜態(tài)資源。

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

如何讓 WebView 的頁面請(qǐng)求走本地服務(wù)

兩種方案:

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

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

在下面的示例代碼中 ,cachedPagePaths 存儲(chǔ)著需要緩存的頁面的 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: 還應(yīng)該判斷下 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)化圖片請(qǐng)求

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

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

代碼實(shí)現(xiàn)

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

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

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

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

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

注意先安裝相關(guān)的插件,插件的名字 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('請(qǐng)?jiān)诔跏蓟笞x取');
    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 為空,改成錯(cuò)誤圖片
      return Response.ok(imageData);
    } else {
      return Response.notFound('next');
    }
  }
}

代碼邏輯

  • 在本地文檔目錄的 www 文件夾中準(zhǔn)備了一個(gè) index.html 文件
  • 啟動(dòng)本地 server,通過訪問 http://localhost:8080/index.html 請(qǐng)求本地頁面。
  • server 收到請(qǐng)求后,對(duì)圖片請(qǐng)求進(jìn)行攔截,通過 NetworkImage 返回圖片。

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

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

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

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

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

關(guān)于圖片類型

在示例代碼中,用 RegExp( r'\.(png|image)$',) 判斷是否要響應(yīng)請(qǐng)求。從正則可以看出,以 png 或 image 結(jié)果的圖片都能響應(yīng)請(qǐng)求。判斷 image 是因?yàn)槭纠械膱D片地址是以 image 結(jié)尾的。

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

關(guān)于圖片地址

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

把圖片緩存到磁盤。

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

總結(jié)一下

服務(wù)端染頁面方案

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

瀏覽器渲染方案

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

圖片緩存

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

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

可能你的項(xiàng)目不同,有不同的方案,歡迎一起討論。

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

番外

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

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

相關(guān)文章

最新評(píng)論