这套题还不错,感兴趣的猿可以试一试:前端开发工程师
问题
由于Dart语言在Flutter上关闭了反射,且语言本身也缺乏动态能力,因此在Flutter上实现热更新或动态UI较为困难。
目前已有的一些动态方案:
- 利用原生框架更新
- 桥接动态脚本语言
- 修改引擎(动态桥接增强版)
- XML/JSON配置UI
以上方案,在我看来都不可取!原因这就来一一分析。
利用原生框架更新,实际上就是更新Flutter框架相关的二进制。Flutter应用发布出来的产物主要包括 libflutter.so
,libapp.so
,flutterAssets
,这样,就可以通过Android端原生平台网络请求,动态下发并加载这些产物,从而实现动态更新。
那么为什么这种方案不可取呢?主要原因在于iOS 的应用商店不允许动态下发和加载二进制产物,包括动态库之类的,故此方案只能在Android端实现。我们学习和使用Flutter,其中最重要的一点就是要有跨平台思维,当然,这要求我们技术面要足够广泛,不能像原生开发一样局限于一隅。我们思考一个Flutter技术方案,首要思考的是能不能通用,如果不能通用,只能在一端使用,那么该方案就是没有意义的。Flutter的优势和生命力,在于跨平台,任何方案,如果使Flutter失去跨平台,那还不如使用原生技术开发,实现起来又成熟又快,还去瞎折腾Flutter干啥,岂不是浪费生命?
不管走多远,千万别忘了我们为什么出发的!
我们再来看一下桥接动态脚本语言的方案。本质上就是打包一个动态脚本语言Runtime,目前有打包JavaScript的,有打包Lua的。这些Runtime其实就是打包成C语言的一个动态库,看起来可行,其实与Dart语言交互十分困难。Dart语言目前的FFI接口能力非常弱,并不像Java的JNI一样成熟,通常只能Dart 调用C语言,C反过来调用Dart十分困难,因此它并不是互操作,只是单向调用。那么,通过FFI桥接Dart语言与目标语言(JS或Lua)互操作基本不可取。
那么再转换一下方向,通过Java 的JNI绑定脚本语言的Runtime是否可行呢?在Android原生上,通过JNI桥接Java语言与脚本语言是一种可行的方案,做得比较好的代表有Python的Kivy框架。Kivy就可以通过Python代码动态生成UI界面,但是作为Flutter,目标语言是Dart而不是Java,脚本语言能轻松反射Java类,但是与Dart交互仍然困难,要想交互,主要可以通过序列化,相当于Flutter插件机制一样,大量频繁操作,性能是一个问题。而且代码也很缺乏灵活性。
其实在此方案之上,还有一种增强方案,那就是修改Flutter引擎。Dart语言虚拟机本身是C++开发的,按理说,脚本语言通过C/C++与Dart交互是很容易的,就像Java JNI一样,这只是因为Dart的虚拟机并未暴露内部的C++接口,通过修改引擎,暴露接口,可以方便脚本语言直接调用Dart类。但此方案更不可取。目前Dart语言本身迭代就十分频繁了,变化也大,且其虚拟机十分复杂,可以遇见的,后续几乎无法维护自己拉取的引擎分支。
最后,我们来看一下,基于XML/JSON配置文件动态生成UI的方案。此方案对某些局部样式可能频繁变化的界面,其实是可行的,相当于应用主题一样。它的主要问题在于功能单一,只能针对特定模版的UI,且不能写逻辑,不灵活。
探索
以上分析了一些主要的动态化更新思路,这里给出我正在探索的解决方案。那就是 LuaDardo库。
LuaDardo是我用Dart语言编写的Lua虚拟机,它的名字是由葡萄牙语的两个单词组成,可以翻译成“镖中月”。
Lua本身是巴西人开发的一种以嵌入其他宿主语言为目标的简洁的高性能脚本语言,Lua在葡萄牙语中是月亮的意思。该语言多用于游戏开发,用Lua脚本编写业务逻辑,然后调用底层C++游戏引擎实现渲染。
Lua语言设计十分精巧,通过一个栈完成对宿主语言的互操作。LuaDardo直接使用Dart语言编写Lua虚拟机,这可以让我们以高性能的方式完成Lua与Dart语言的互操作。只需要对Flutter的Widget进行一定封装,将Dart类绑定到Lua语言中,即可使用Lua脚本编写UI界面。
另外,LuaDardo直接基于Dart开发,天然的具备跨平台能力,只要有Flutter的地方,就能使用。即使是Flutter的桌面应用,亦可具备这种动态脚本能力。
LuaDardo本身定位是基于Dart语言的虚拟机,我们要用Lua写Flutter界面,还需要对Flutter控件的封装扩展。
flutter_lua_dardo 就是一个这样的扩展库。该库主要用于包装Flutter接口和控件,这使得Flutter能够在需要经常改变UI风格的地方使用远程脚本动态地更新和生成界面。
请注意,flutter_lua_dardo 只是一个实验性的探索。它只封装了几个简单的Widget。欢迎其他人一起来探索,为Lua封装更多的Widget。
另外的,LuaDardo库已完成了大部分工作,只是Lua 自带的标准库尚未完全编写完毕,协程库、OS库、IO库等尚未开始。
例子
用法
新建 Lua 脚本 test.lua
:
function getContent1()
return Row:new({
children={
GestureDetector:new({
onTap=function()
flutter.debugPrint("--------------onTap--------------")
end,
child=Text:new("click here")}),
Text:new("label1"),
Text:new("label2"),
Text:new("label3"),
},
mainAxisAlign=MainAxisAlign.spaceEvenly,
})
end
function getContent2()
return Column:new({
children={
Row:new({
children={Text:new("Hello"),Text:new("Flutter")},
mainAxisAlign=MainAxisAlign.spaceAround
}),
Image:network('https://gitee.com/arcticfox1919/ImageHosting/raw/master/img/flutter_lua_test.png'
,{fit=BoxFit.cover})
},
mainAxisSize=MainAxisSize.min,
crossAxisAlign=CrossAxisAlign.center
})
end
添加依赖 pubspec.yaml
dependencies:
flutter_lua_dardo: ^0.0.2
添加Dart代码:
class _MyHomePageState extends State<MyHomePage> {
LuaState _ls;
bool isChange = false;
@override
void initState() {
loadLua();
super.initState();
}
// 加载Lua虚拟机
void loadLua() async {
String src = await rootBundle.loadString('assets/test.lua');
try {
LuaState ls = LuaState.newState();
ls.openLibs();
FlutterLua.open(ls);
FlutterWidget.open(ls);
ls.doString(src);
setState(() {
_ls = ls;
});
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: _ls == null
? CircularProgressIndicator()
// 调用Lua函数,创建UI
: FlutterWidget.findViewByName<Widget>(
_ls, isChange ? "getContent2" : "getContent1"),
),
floatingActionButton: FloatingActionButton(
child: Icon(CupertinoIcons.arrow_swap),
onPressed: (){
setState(() {
isChange = !isChange;
});
},
),
);
}
}