真当Flutter不能热更新?QQ团队开源动态化Flutter
作者:SoapY
原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea
基于JS的高性能Flutter动态化框架
这可能是目前放出来的相对最完整的Flutter动态化方案
缘起:18年10月份,我们团队的iOS产品尝试引入 Flutter,做为iOS开发,一接触到Flutter就马上感受到,Flutter 虽然强大,但不能动态化是阻碍我们使用她的唯一障碍了。舍弃Native的开发方式,一个很大的诉求是获取动态更新的能力。看Google团队对动态化的措辞,应该指望不上了,自己动手丰衣足食。
简介
项目代号:MXFlutter (Matrix Flutter)
核心思路是把 Flutter 的渲染逻辑中的三棵树中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整实现了 Flutter 控件层封装,可以使用 JavaScript,用极其类似 Dart 的开发方式,开发Flutter应用,利用JavaScript版的轻量级Flutter Runtime,生成UI描述,传递给Dart层的UI引擎,UI引擎把UI描述生产真正的 Flutter 控件。所以,他在iOS上是完全动态化的 ,完整代码在github:
如果能帮助到大家,请给MXFlutter点个Star,给我们有动力继续更新下去^_*,也让整个Flutter社区都能了解到我们开发者的贡献。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter)
继续前先瞥一眼整体的架构,一句话介绍MXFlutter,就是用JavaScript,以Flutter的写法开发Flutter。汗...还是有点绕,大家看下面贴出来的代码吧。
效果
先看看使用效果,以下截图是在MXFlutter框架下用JS开发,大家可以把上面的源码下载下来,里面有完整的JS代码示例:
这个是APP示例截图
下面是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,已经接近在线上版直接使用了。
现状
MXFlutter虽然各个模块已相对完整,但投入生产还需要解决其中的BUG,由于19年初,小组启动新项目,非常繁忙,几乎没有时间继续开发,从3月份一直暂停,目前人力仍然很紧张,如果大家有兴趣,期待小伙伴们一起加入,共同丰富 MXFlutter 动态化能力。
00 分享下动态化探索过程中的几个炮灰方案
Flutter 动态化方案一:静态解析Dart语言,生成UI描述
Dart 本身是描述语言,IDE 的 Outline 工具可以解析 Dart 代码生成树形结构,我们可以利用其源码,生成 JSON UI 描述,相关代码:github.com/flutter/flu…dart-sdk: analysis_server
静态解析 Dart 缺点,不能写逻辑,对编写UI代码有很多限制,不能写判断语句,不能写函数,要支持这些成本很高。所以只好放弃。
快速介绍下Flutter的核心渲染模块三棵树
响应式UI框架
WidgetTree:Widget 里面存储了一个视图的配置信息,可以高效的创建(build)和销毁
Element 是分离 WidgetTree 和真正的渲染对象的中间层, WidgetTree 用来描述对应的Element 属性
RenderObject 来执行 Diff, Hit Test 布局、绘制
第一棵树有完整的UI描述信息,那么我只要JIT下通过 DartVM 创建第一棵树,其他耗时的操作都丢到AOT里去。
Flutter 动态化方案二:动态运行 Dart 语言,生产UI描述
和方案一静态解析Dart对比,第二个方案是写一个极其轻量的运行时库,让编写UI的Dart 代码运行了起来,生成树形结构,再序列化为 JSON(debug),FlatBuffers (release)UI 描述。可以称之为动态解析方案
具体渲染逻辑
总体架构
架构也有了,方案也有了,要Run起来还有几个麻烦事要忙活,DartVM 要抽出来,Dart JIT层的轻量级运行时库,Dart AOT层把DSL转成真正Widget的UIEngin也要写哦,就是图中黄色和红色的三部分
抽离DartVM
无法简单修改编译条件抽离
Dart源代码在进行编译时会通过DART_PRECOMPILED_RUNTIME宏进行条件编译从而在Debug版编译JIT模式,Release版编译AOT模式。并且这两种模式是互斥的,无法同时存在。
简单的解决方法是
我们单独编译出一个DartVM,打包成动态库,修改导出符号,避免符合冲突
引入DartVM还需要的工作
开发DartVM与Native互通接口,参考了Flutter,使用Native Extension和Dart_Invoke实现互相调用
双DartVM调试方案,两个DartVM独立运行,通过远程端口单独调试DartFlutter
支持引入第三方库,DartFlutter在打包发布时会通过shell脚本分析.packages文件将依赖库自动打包随Dart File Zip一起随包下发。常用库可以预先打包的App本地,减少下发文件大小
一个暂时无法解决的问题
安装包过大,DartVM增大安装包30M,如果加上原本的AOT40M,整个Flutter安装包会增大到70M,用DartVM不现实。怎么办呢。
01 最终方案 JavasSriptCore 替换 DartVM
可性能分析
JavasSriptCore 是iOS官方库,不增加安装包
Dart代码和JS代码非常相近,可以用工具转换
JavasSriptCore 与 Native有更方便的互调接口
ReactNative 已验证通过JS开发App能力是可行的
JS的执行效率是DartVM的3倍编码1M的JSON只需 2毫秒
需要解决的问题
用JS开发假的Flutter Runtime 封装JavasSriptCore与Native、 Flutter互调接口
02 讲解下 MXFlutter 的渲染原理
渲染树
两个重要的数据结构
MXScriptWidget
MXWidgetTree
MXScriptWidget管理一个Script页面或控件,负责创建管理 ScriptWidgetTree,以自增ID与Flutter对应Widget相互调用 ,每次Build都会创建一个新的MXWidgetTree
MXFlutter 事件
在 JS 侧 buildWidget 时,我们会对 function 事件,生成自增的唯一 callbackID,并与 widgetID 组合拼接成 widgetID/callbackID,作为事件的唯一标识。用户点击界面某个 button 时,事件由 Flutter 侧传到 JS 侧,通过解析 widgetID/callbackID,找到对应 widget 的 callback,完成事件处理。
MXFlutter 高效的动态列表
通过在 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]);},
)
MXFlutter 动画的方案
动画参数在VM层配置一次,动画开始后在Flutter层闭环循环rebuild,形成动画效果,这个是比较通用的做法了。
03 渲染优化
不管JSWidget创建有多快,总是有跨语言执行,所以减少Build次数和减小Build出来的DSL UI描述大小,可以优化性能。
渲染优化1-局部刷新:配置树Diff
一个事实
自动对比两次Widget 无论如何都没有直接创建一个新的快,如果开发者不参与,由框架来自动计算Diff是得不偿失的
可行的方法
牺牲响应式UI框架的设计模式 采用和Native、Web的方式,由开发者参与自己设置Diff的节点,即根据ID获取对应Widget,修改Widget参数,Rebuild生成新DSL
渲染优化2-局部刷新-嵌套节点
MXScriptWidget 是一个具备Build WidgetTree,缓存Callback映射表,动画支持的基本单位。可以作为普通FlutterWidget来使用。
在Flutter层,如果Widget树中节点有MXScriptWidget,则在对应节点上创建MXFlutterWidget自定义控件
两个子树可以相互对应获得局部刷新,callback回调,动画支持,Rebuild时所生产的UI DSL 很大减少,加快刷新速率
渲染优化3-可以分离动态和静态控件
MXStatelessWidget 可以通过使用无状态的ScriptWidget来向框架标示,其下面的子树,在每次build中不会变化,其build结果会被缓存,下次在Flutter层直接复用
内存-跨层镜像对象的生命周期
VM层,Flutter层,Native层镜像对象的生命周期如何控制?参考 iOS JavaScriptCore 和 Objective-C的解决方法
以Flutter层的对象生命周期为主
在VM层增加WeakMap支持,不增加对象引用计数,Flutter层释放之后,释放VM层对象
在Native层使用 JSManagerValue,VM层对象释放后,Native的引用被自动置空
线程问题
参照业界RN等框架的设计,VM层跑在一个单独的后台线程
从Flutter层通过Native通道调用到VM,发生两次线程切换
Flutter UI层和MXScript层是异步调用,限制动态控件的架构设计
一个可行方案 修改FlutterEngine ,定制开发Dart->Native->VM 这个通道,调用到VM不切换线程 VM不新建线程,直接由Flutter UI Thread 消息循环驱动,这样也同时支持了和Flutter UI 层的高效同步调用,但要注意从Native调用到VM,需要通过定制FlutterEngine的接口。
04 让开发者写出优雅的代码
让开发者写出优雅的代码,咳咳,这里有点吹了,总之,我们想让使用MXFlutter的开发同学写出来的代码看来正规一些,好看一些。
完美支持Dart Flutter语法
定义所有Flutter 中同名Widget类,构建Widget的参数类,支持相同的Build方式,SetState触发刷新,事件响应函数
Callback函数自动生成CallbackID
Callback函数自动This绑定
ListView 像Dart层一样开发,支持itemBuilder回调函数
参考JS示例源码TGIF-iMatrix home_page.js(https://github.com/TGIF-iMatrix/MXFlutter/blob/master/js_flutter_src/app_test/home_page.js)
05 MXFlutter 基础建设
因为 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 老王。
真当Flutter不能热更新?QQ团队开源动态化Flutter相关推荐
- 真当Flutter不能热更新?QQ团队开源动态化Flutter,稳进大厂
pinned: true, expandedHeight: _kAppBarHeight, actions: [ IconButton({ icon: new Icon(new IconData(1) ...
- flutter不支持热更新_真当Flutter不能热更新?众能动态化Flutter
效果展示 上面铁军中的这个关于我们页面以及转换为了动态化页面. 动态化标签 动态化路由 一个flutter页面转换为动态化页面,只需要添加动态化标签和动态化路由就行了,对业务页面的开发修改很少,相比其 ...
- flutter不支持热更新_在iOS原生项目中使用Flutter,热更新
前言: Flutter 因其自建的渲染引擎,背靠谷歌的支持,近来俘获了不少的开发小伙伴,越来越多的开发者尝试使用Flutter进行开发,在原生项目中嵌入Flutter来完成复杂度不高的页面成为了一个不 ...
- 手游热更新方案xLua开源:Unity3D下Lua编程解决方案
转载:https://mp.weixin.qq.com/s/2bY7A6ihK9IMcA0bOFyB-Q 导语 xLua是Unity3D下Lua编程解决方案,自2016年初推广以来,已经应用于十多款腾 ...
- 【腾讯Bugly干货分享】手游热更新方案xLua开源:Unity3D下Lua编程解决方案
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/2bY7A6ihK9IMcA0bOFyB-Q 导语 xL ...
- 腾讯QQ团队开源分布式后台毫秒服务引擎全解析:引擎架构、RPC、灰度……
腾讯QQ团队将于12月4日开源一个服务开发运营框架,叫做毫秒服务引擎(Mass Service Engine in Cluster,MSEC),它集RPC.名字发现服务.负载均衡.业务监控.灰度发布. ...
- 腾讯QQ团队开源分布式后台服务引擎msec
转载:http://www.devstore.cn/essay/essayInfo/6851.html 在服务器端程序开发领域,性能问题一直是备受关注的重点.业界有大量的框架.组件.类库都是以性能为卖 ...
- flutter已经支持安卓热更新_flutter 在 android 上的热更新
热更新是一种需求吧. 自然会想到flutter 是否支持热更新. 然后一些群里问了问普遍反映不可以热更新,还说咸鱼的文章写了不支持热更新. 然后我表示很怀疑. 我的结论可以做到热更新 1.你需要把fl ...
- flutter不支持热更新_Flutter 在安卓上可以实现热更新了
本文由 句号君 授权投稿 原文链接:https://blog.csdn.net/qizewei123/article/details/102963340 Flutter 官方在 GitHub 上声明是 ...
最新文章
- “神经+符号”:从知识图谱角度看认知推理的发展
- 关闭Eclipse光标悬浮自动提示
- 【总结】找到自适合的学习方法
- Android:Intent传递数据
- 开源一个WEB版本GEF,基于SVG的网页流程图框架
- php 读取excel表数据
- GAMP PPP部分学习与流程解析
- 运维工程师的打怪升级之路
- PS剪切蒙版怎么用?
- python判断是否为英文_Python判断字符串是否纯英文(纯ASCII码字符)
- 计算机无法识别无线USB网卡,Win10 usb无线网卡无法识别连接如何解决?
- 分析锂电池充放电保护电路的特点及工作原理
- 【计算机网络四】网络层
- origin画图_Origin作图过程中如何让图看起来更生动
- 视频封面如何统一修改怎么设置为同一张图片
- Python 淘宝系列(三): 模拟登陆成功后获取购物车信息
- python使用pyecharts库画地图数据可视化
- NLP之NLG入门理解
- Redis主从配置,简单滴很
- 软件技术发展的驱动力
热门文章
- win 10 源码安装 Davinci
- BugkuCTF_MISC——“多种方法解决”、“闪的好快”
- android scrollview 动画滚动,Android使用ScrollView实现滚动效果
- StampedLock 深入分析
- 玩转数据结构(十三)构建BST
- java_note9
- 处理jquery.jqprint.js不能打印input、textarea区以及样式问题
- warning: adding embedded git repository: NeteaseCloudMusicApi
- Python numpy.prod()函数
- 汤唯原来这样捞钱的!