本文记录了基于WebView的Flutter数据可视化库echarts_flutter的性能优化。
对于任何基于WebView的小部件,页面的加载始终是性能的关键部分。 echarts_flutter也不例外 ,其基础是使用WebView呈现echarts的本地页面。
echarts_flutter的加载内容可以分为以下几部分:
- 模板HTML
- echarts脚本
- echarts扩展脚本
- 图表的逻辑代码
图表的模板HTML和逻辑代码很小,因此关键是加载echarts脚本和echarts扩展脚本。
echarts的最佳功能之一是它具有许多令人敬畏的扩展,例如WebGL 3D图表,GIS地图等。随着数据可视化要求变得越来越复杂,这些扩展与echarts本身一样重要。 因此,必须允许用户方便地导入扩展。 此外,为了避免麻烦的资产管理,我们希望将HTML和JavaScript都当作字符串来处理,因此WebView会将所有源作为URI加载。
会有一些问题:
- 脚本应该在HTML内还是随后注入?
- URI有一些字符限制,它需要一种安全的编码形式。
原始方法
一开始,我们认为,总的来说,我们最好将所有内容放入HTML中并将它们一起加载。 考虑到JavaScript中有很多非法的URI字符,我们应该在编写后将HTML转换为Base64。 因为我们不知道用户将导入什么扩展脚本,所以编码将由函数动态执行:
String _getHtml(
String echartsScript,
List< String > extensions,
String extraScript,
) {
... // Compose and return all HTML and scripts
}
@override
void initState() {
super .initState();
// Convert to Base64 in init
_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(
// Load all of them
initialUrl: _htmlBase64,
...
);
}
性能测试
让我们对羽毛分析进行简单的性能测试。 该测试用例具有三个图表,包括WebGL 3D图表和液体动画图表:
使用Flutter Dev Tool,我们可以获得CPU时间占用的明火图:
优化
图表及其扩展量很大。 因此,在运行时编写和转换字符串将花费大量时间。 但是这些是获取合法URI字符串的必要步骤,那么如何解决此问题呢?
放弃“将所有内容一起加载”的想法,然后通过valuateJavascript注入动态部分,而仅将静态部分放入HTML中,该怎么办? 这样可以节省一些转换工作。
为了确保可行性,让我们先做一个实验:仅从HTML中移出所有脚本,然后将它们与validateJavascript一起注入,然后检查性能:
@override
void initState() {
super .initState();
_htmlBase64 = 'data:text/html;base64,' + base64Encode(
const Utf8Encoder().convert(_getHtml(
// remove all scripts form the convert function
// 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( '' '
// inject after the page is loaded
$echartsScript
$extensionsStr
const chart = echarts.init(document.getElementById(' chart '), null);
${this.widget.extraScript}
chart.setOption($_currentOption, true);
' '' );
}
结果是:
我们可以看到,加载HTML的时间减少了,而包含脚本注入的onPageFinished的时间却增加了。 总时间减少了。
因此,似乎转换大型字符串的成本很高。 相反,使用评估Java脚本是正确的方法。
因此,我们然后删除了所有动态转换部分,并将模板HTML加载为const字符串。 由于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格式放入脚本”并不一定比“由valuateJavascript注入”更好,并且由于某些编码原因甚至花费更多时间。
结论
总之,最终的优化解决方案是:将模板HTML加载到UTF-8 URI字符串中,并使用EvaluationJavascript注入所有脚本和逻辑代码。
注意:webview_flutter存在一个问题,即onPageFinished在IOS中不起作用,因此上述优化目前尚未应用于发行版。 您可以在 此commit中 查看其源代码 。
先前发布在https://levelup.gitconnected.com/a-performance-optimization-of-flutter-webview-6afa1a5b4300
From: https://hackernoon.com/a-performance-optimization-of-flutter-webview-8f5336wa