本文記錄了基於 WebView 的 Flutter 可視化庫:echarts_flutter 的一次優化加載性能的過程。css
對於任何基於 WebView 的組件,html 的加載都是關乎性能的一個重要環節。 echarts_flutter 的基本原理是用 WebView 渲染本地的 echarts 圖表,所以也不例外。html
echarts_flutter 的 WebView 加載主要涉及如下幾個部分:git
其中模板 html 和圖表邏輯代碼的體量很小,重點是 echarts 本體及擴展腳本加載。github
Echarts 最強大的功能之一,就是具備不少功能強大的擴展,好比 WebGL 3D圖表、Map 地圖組件,在數據可視化要求愈來愈高的今天,這些擴展幾乎成了和本體同樣重要的部分,所以容許用戶方便的引入擴展是一個必不可少的功能。此外爲了不麻煩的 asset 管理,咱們但願不管是 HTML 仍是 JavaScript 腳本,都能以字符串的方式處理,即 WebView 加載統一資源定位符(URI)。web
所以這其中就有如下幾個問題:安全
最初的時候,方案是這樣考慮的:按照通常理解內容儘可能都放在 HTML 中一次加載是最好的。考慮到 JavaScript 腳本中有大量的 URI 限制字符,組裝完 HTML 後轉換成 Base64 編碼。因爲事先不知道用戶引入的腳本,編碼轉換經過函數動態完成:性能優化
String _getHtml(
String echartsScript,
List<String> extensions,
String extraScript,
) {
... // 拼接並返回全部 HTML 和腳本
}
@override
void initState() {
super.initState();
// 初始化的時候進行 Base64 轉換
_htmlBase64 = 'data:text/html;base64,' + base64Encode(
const Utf8Encoder().convert(_getHtml(
echartsScript,
widget.extensions ?? [],
widget.extraScript ?? '',
))
);
_currentOption = widget.option;
}
@override
Widget build(BuildContext context) {
return WebView(
// 加載全部內容
initialUrl: _htmlBase64,
...
);
}
複製代碼
爲進行性能分析,進行一個簡單初步的性能測試。用例是加載三個圖表,其中第二個引入了 WebGL 渲染 3D 的圖表,第三個引入帶動畫的水球圖:bash
利用 Flutter Dev Tool 中的 CPU 火焰圖,能夠看到時間佔用以下:echarts
Echarts 本體和不少擴展的腳本體積都很是大,在運行時拼接字符串和編碼轉換無疑都是很耗時的,可是經過 URI加載的話爲保證合法又是必要的,如何解決這個矛盾呢?async
不如捨棄「一次所有加載」的想法,把不定的、動態的部分經過 evaluateJavascript
函數插入,無需編碼轉換;把肯定的、靜態的事先轉碼好直接加載。
爲此,先作個實驗,其餘條件都不動,僅把全部的腳本(Echarts 本體、擴展)移出 HTML ,用 evaluateJavascript
函數插入,看性能變化如何:
@override
void initState() {
super.initState();
_htmlBase64 = 'data:text/html;base64,' + base64Encode(
const Utf8Encoder().convert(_getHtml(
// 將編碼轉換中傳入的全部腳本去掉
// echartsScript,
// widget.extensions ?? [],
// widget.extraScript ?? '',
))
);
_currentOption = widget.option;
}
void init() async {
final extensionsStr = this.widget.extensions.length > 0
? this.widget.extensions.reduce(
(value, element) => (value ?? '') + '\n' + (element ?? '')
)
: '';
await _controller?.evaluateJavascript(''' // 改在頁面加載完成後注入 $echartsScript $extensionsStr const chart = echarts.init(document.getElementById('chart'), null); ${this.widget.extraScript} chart.setOption($_currentOption, true); ''');
}
複製代碼
結果以下:
能夠看到,加載部分的耗時減小了,而包含插入腳本的 onPageFinished
函數耗時增長了,總耗時減小了很多。
看來對大段字符串的編碼轉換確實性價比低,改用 evaluateJavascript
函數插入是個可行的方向。
這樣咱們再把全部的動態編碼邏輯去掉,模板 HTML 直接以常理字符串加載。並且因爲如今的 HTML 靜態、簡短,咱們能夠手動轉換非法字符,直接傳入 UTF-8 編碼,這樣咱們的組件就無需引入 dart:convert 庫了,並且源碼更直觀。
const htmlUtf8 = 'data:text/html;UTF-8,<!DOCTYPE html><html><head><meta charset="utf-8"><style type="text/css">body,html,%23chart{height: 100%;width: 100%;margin: 0px;}div {-webkit-tap-highlight-color:rgba(255,255,255,0);}</style></head><body><div id="chart" /></body></html>';
@override
void initState() {
super.initState();
_currentOption = widget.option;
}
@override
Widget build(BuildContext context) {
return WebView(
initialUrl: htmlUtf8,
...
);
}
複製代碼
這樣測試結果以下:
能夠看到,耗時又有進一步的減小,主要體如今加載部分。
這樣相對於最初的時候,性能提高仍是比較大的。
Echarts 本體的腳本也是肯定的、靜態的,若是把它事先放在HTML裏,並事先轉好碼呢:
const echartsHtmlBase64 = '...';
@override
Widget build(BuildContext context) {
return WebView(
initialUrl: echartsHtmlBase64,
...
);
}
複製代碼
結果以下:
相比以前優化的結果耗時反而更多了。
可見「腳本放在 HTML 中「並不必定比」 evaluateJavascript
函數插入」好,甚至因爲編碼等緣由,反而可能更耗時。
綜上,最終的優化方案就採用:模板 HTML 以UTF-8 URI 字符串的形式加載,全部腳本和邏輯代碼以 evaluateJavascript
函數插入。