多是目前放出来的相对最完整的Flutter动态化方案html
跨平台新明星Flutter推出后,咱们团队也尝试引入 Flutter ,作为iOS开发,立刻感觉到,Flutter 虽然强大,但不能像RN同样动态化是阻碍咱们使用她的惟一障碍了。看Google团队对动态化的计划,短时间内应该不会上线,因此本身动手,启动了这个技术探索项目。java
项目代号:MXFlutter (Matrix Flutter)git
核心思路是把 Flutter 的渲染逻辑中的三棵树中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整实现了 Flutter 控件层封装,可使用 JavaScript,用极其相似 Dart 的开发方式,开发Flutter应用,利用JavaScript版的轻量级Flutter Runtime,生成UI描述,传递给Dart层的UI引擎,UI引擎把UI描述生产真正的 Flutter 控件。因此在iOS上是彻底动态化的 ,预览版的完整代码在github:github
若是能帮助到你们,请给MXFlutter点个Star,给咱们有动力继续更新下去^_*,也让整个Flutter社区都能了解到咱们中国开发者的贡献。github TGIF-iMatrix MXFluttershell
继续前先瞥一眼总体的架构,一句话介绍MXFlutter,就是用JavaScript,以Flutter的写法开发Flutter。汗...仍是有点绕,你们看下面贴出来的代码吧。设计模式
如下截图是在MXFlutter框架下用JS开发,你们能够把上面的源码下载下来,里面有完整的JS代码示例:数组
下面是UI截图对应的JS代码,没错,你没有眼花,这个真的是 JavaScript 代码,能够在 MXFlutter 的运行时库上渲染出 Flutter 的UI缓存
class JSPestoPage extends MXJSWidget {
constructor() {
super("JSPestoPage");
this.recipes = recipeList;
}
build(context) {
let statusBarHeight = 24;
let mq = MediaQuery.of(context);
if (mq) {
statusBarHeight = mq.padding.top
}
let w = new Scaffold({
appBar: new AppBar({
title: new Text("Pesto Demo")
}),
floatingActionButton: new FloatingActionButton({
child: new Icon(new IconData(0xe3c9)),
onPressed: this.createCallbackID(function () {
}),
}),
body: new CustomScrollView({
semanticChildCount: this.recipes.length,
slivers: [
//this.buildAppBar(context, statusBarHeight),
this.buildBody(context, statusBarHeight),
],
}),
//body:this.buildItems()[0]
});
return w;
}
buildAppBar(context, statusBarHeight) {
return SliverAppBar({
pinned: true,
expandedHeight: _kAppBarHeight,
actions: [
IconButton({
icon: new Icon(new IconData(1)),
tooltip: 'Search',
onPressed: this.createCallbackID(function () {
}),
}),
],
flexibleSpace: LayoutBuilder({
builder: function (context, constraints) {
size = constraints.biggest;
appBarHeight = size.height - statusBarHeight;
t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);
extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);
logoHeight = appBarHeight - 1.5 * extraPadding;
return Padding({
padding: EdgeInsets.only({
top: statusBarHeight + 0.5 * extraPadding,
bottom: extraPadding,
}),
child: Center({
child: new Icon(new IconData(1))
}),
});
},
}),
});
}
buildBody(context, statusBarHeight) {
let mediaPadding = EdgeInsets.all(0);
let mq = MediaQuery.of(context);
if (mq) {
mediaPadding = MediaQuery.of(context).padding;
}
let padding = EdgeInsets.only({
top: 8.0,
left: 8.0 + mediaPadding.left,
right: 8.0 + mediaPadding.right,
bottom: 8.0
});
return new SliverPadding({
padding: padding,
sliver: new SliverGrid({
gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({
maxCrossAxisExtent: _kRecipePageMaxWidth,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
}),
delegate: new SliverChildBuilderDelegate(
function (context, index) {
let recipe = this.recipes[index];
let w = new RecipeCard({
recipe: recipe,
onTap: function () { showRecipePage(context, recipe); },
});
return w;
},
{
childCount: this.recipes.length,
}),
}),
});
}
复制代码
源码中还有更丰满的示例,高仿知乎页面JSFlutter版 github.com/TGIF-iMatri… ,这是对应UI,已经接近在线上版直接使用了。bash
这个漂亮的知乎页面,是用Dart版转JS而来,在此鸣谢做者许吉友 ,你们能够关注一下他。数据结构
MXFlutter虽然各个模块已相对完整,但投入生产还须要解决其中的BUG,因为19年初,小组启动新项目,很是繁忙,几乎没有时间继续开发,从3月份一直暂停,目前人力仍然很紧张,因此先放出技术方案和预览版代码,若是你们有兴趣,期待小伙伴们一块儿加入,共同丰富 MXFlutter 动态化能力。
Dart 自己是描述语言,IDE 的 Outline 工具能够解析 Dart 代码生成树形结构,咱们能够利用其源码,生成 JSON UI 描述,相关代码:github.com/flutter/flu… dart-sdk: analysis_server
静态解析 Dart 缺点,不能写逻辑,对编写UI代码有不少限制,不能写判断语句,不能写函数,要支持这些成本很高。因此只好放弃。
响应式UI框架
第一棵树有完整的UI描述信息,那么我只要JIT下经过 DartVM 建立第一棵树,其余耗时的操做都丢到AOT里去。
和方案一静态解析Dart对比,第二个方案是写一个极其轻量的运行时库,让编写UI的Dart 代码运行了起来,生成树形结构,再序列化为 JSON(debug),FlatBuffers (release)UI 描述。能够称之为动态解析方案
具体渲染逻辑
整体架构
架构也有了,方案也有了,要Run起来还有几个麻烦事要忙活,DartVM 要抽出来,Dart JIT层的轻量级运行时库,Dart AOT层把DSL转成真正Widget的UIEngin也要写哦,就是图中黄色和红色的三部分
Dart源代码在进行编译时会经过DART_PRECOMPILED_RUNTIME宏进行条件编译从而在Debug版编译JIT模式,Release版编译AOT模式。而且这两种模式是互斥的,没法同时存在。
咱们单独编译出一个DartVM,打包成动态库,修改导出符号,避免符合冲突
安装包过大,DartVM增大安装包30M,若是加上本来的AOT40M,整个Flutter安装包会增大到70M,用DartVM不现实。怎么办呢。
用JS开发假的Flutter Runtime 封装JavasSriptCore与Native、 Flutter互调接口
两个重要的数据结构
MXScriptWidget管理一个Script页面或控件,负责建立管理 ScriptWidgetTree,以自增ID与Flutter对应Widget相互调用 ,每次Build都会建立一个新的MXWidgetTree
在 JS 侧 buildWidget 时,咱们会对 function 事件,生成自增的惟一 callbackID,并与 widgetID 组合拼接成 widgetID/callbackID,做为事件的惟一标识。用户点击界面某个 button 时,事件由 Flutter 侧传到 JS 侧,经过解析 widgetID/callbackID,找到对应 widget 的 callback,完成事件处理。
经过在 JS 侧,ListView 调用 Build 方法时,提早展开 child, 并为 ListView 增长 children 成员变量。此时,由于仅有数据配置,不会有多余的 Layout 过程,因此速度是很是快的。
preBuild(jsWidget, buildContext) {
if(this.builder) {
for (let i = 0; i < this.childCount; ++i) {
let w = this.builder(buildContext, i);
this.children.push(w);
}
delete this.builder;
}
super.preBuild(jsWidget, buildContext);
}
复制代码
在 Flutter 侧,ListView 仍然是动态建立,滑动列表,MXFlutter Engine 根据 Children 数组里的配置数据,建立真正的 Flutter WidgetCell,效率与原生相同彻底一致。
ListView.builder(
itemCount: children.length,
itemBuilder: (context, index) {
return UIEngine.toWidget(children[index]);
},
)
复制代码
动画参数在VM层配置一次,动画开始后在Flutter层闭环循环rebuild,造成动画效果,这个是比较通用的作法了。
无论JSWidget建立有多快,老是有跨语言执行,因此减小Build次数和减少Build出来的DSL UI描述大小,能够优化性能。
自动对比两次Widget 不管如何都没有直接建立一个新的快,若是开发者不参与,由框架来自动计算Diff是得不偿失的
牺牲响应式UI框架的设计模式 采用和Native、Web的方式,由开发者参与本身设置Diff的节点,即根据ID获取对应Widget,修改Widget参数,Rebuild生成新DSL
MXStatelessWidget 能够经过使用无状态的ScriptWidget来向框架标示,其下面的子树,在每次build中不会变化,其build结果会被缓存,下次在Flutter层直接复用
VM层,Flutter层,Native层镜像对象的生命周期如何控制? 参考苹果 iOS JavaScriptCore 和 Objective-C的解决方法
参照业界RN等框架的设计,VM层跑在一个单独的后台线程
让开发者写出优雅的代码,咳咳,这里有点吹了,总之,咱们想让使用MXFlutter的开发同窗写出来的代码看来正规一些,好看一些。
参考JS示例源码 TGIF-iMatrix home_page.js
由于 JavaScript 不支持模块化开发,不能引用其余文件代码,咱们参照 RN,使用 Node.js 的模块化代码,在Native 层支持 require 语法。开发时,IDE最好选用 VSCode,由于能够按装JS插件,直接运行调试JS
另外,咱们经过重定向模拟器 JS 路径文件到开发机,用户修改完 JS 文件,即可直接看到相应修改,实现模拟器的页面热更新。
因为时间紧张,MXFlutter还有不少遗留的问题,做为一个技术探索,很是辛苦但很是有趣,期待各位大牛指导,期待小伙伴们提出问题一块儿讨论解决。
要了解所有,必定要拉下源码,运行起来看看,有问题能够留言一讨论,MXFlutter会持续更新。
项目成员luca浪哥,nice,yockie帅哥贡献了动画,控件,示例APP等核心实现, chaodong老师负责了DartVM方案,IP老师帮忙提供了单元测试,健身大神yofer老师负责了代码维护,工具建设。
TGIF-iMatrix 是一个技术氛围浓厚,有美女有帅哥有趣有爱的团队,还有精通量子计算,5G等前沿技术的数据分析victor老王,欢迎iOS,Android开发小伙伴,数据开发,数据分析岗位同窗投递简历哦:imatrixteam@qq.com
对MXFlutter有兴趣的小伙伴,能够加QQ群交流 QQ群:747535761
另外给咱们的项目作个小广告,你们轻拍, 看点视频-腾讯短视频,年轻人都爱看 看点视频 apps.apple.com/cn/app/id14…