Thresh — 基于JS的Flutter动态化方案

Thresh提供了一个简单、高效的应用开发框架和丰富的组件及API,帮助开发者在前端开发中具有原生 APP 体验的服务,是满帮集团开源的一套稳定、高性能的跨端动态化方案。

方案介绍

Google官方对于Flutter动态化也模棱两可,android侧可支持更新产物来达到相对动态,iOS端完全不支持,这样我们的动态化是无法实现了吗?结果当然是No,在Flutter中执行的语言是Dart,Dart是声明式语言,写法和JS很像,所以试想下如果将JS转换为Dart,这样DSL既能对齐app内的其他动态化方案(如RN等),也能做到动态更新,同时也为了解决业务需求的快速迭代上线以及多端复杂业务逻辑不一致等问题,基于此,在实施过程中我们参考小程序的DSL设计同时也借鉴了RN方案的通信思想,推出了我们满帮集团的Flutter动态化方案。

Thresh与其他开源跨端动态化方案对比

特性/对比RN【facebook】Thresh【满帮】
DSLJS/TSJS/TS
渲染引擎JSCJSC/V8
快速部署支持支持
跨端兼容性适配一般较好
调试模式较好一般
Hot reload支持支持
底层渲染引擎不统一统一
页面更新组件级别组件级别

方案实现

在动态化设计中,DSL 设计尤其重要,作为客户端已逐步向大前端融合进程的一部分,我们选定 js 作为DSL,同时也为了未来能适配RN的DSL以及内部的其他动态化方案;核心思路是把 Flutter 的页面渲染逻辑中的三棵树中的第一棵,通过 js 来构造。这其中要完成 js与 Flutter 层完成基础组件映射,通过js引擎来生成UI描述,并传递给Dart层的 UIEngine,UIEngine 把UI描述转换为 Flutter 控件,最终渲染成页面。

系统整体设计思路
image1

UI层:主要使用JavaScript定义了与Flutter同功能的基础组件,主要是对Flutter WidgetTree进行描述,通过这些组件和API来构造成前端业务页面。

Native层:

1、JS(JavaScriptCore、V8)引擎来加载并执行UI层包(xxx业务_js.bundle),注册被动方法来被JavaScript 调用,如bridge、事件响应、异常上报等等;

2、维护Flutter engine 、Channel实例,与Flutter建立通道,作为JavaScript 与 Flutter通信的媒介;以及维护UI 线程、任务线程队列。

Flutter层:主要维护JavaScript转换为Dart的Engine,并支持缓存,异步更新组件;事件通信以及页面的生命周期管理等等。

动态化flutter 框架主要由这三部分构成,每一部分都处理不同的逻辑并耦合事件通信来更新渲染页面、事件响应,主要完成UI组件定义并转换,JavaScript与Native,Native与Flutter之间的通信,并支撑多个业务接入。

UI层介绍

参考微信小程序基础组件,Thresh框架基本完成了这些常用组件的开发,来支撑现有业务场景的接入,语法定义规则也是如此,支持React,并为未来支持ReactNative代码转换做准备;Flutter的核心渲染主要由三棵树构成,WidgetTree、ElementTree以及RenderObjectTree,WidgetTree包含一些配置信息,用于build和销毁,这里面UI 层主要处理与widgetTree相关的组件映射、转换等,ElementTree与RenderObjectTree后面的原理文章再做介绍;

现支持的组件列表以及其部分属性:【基础组件

image-20200329213237962 image-20200329213237962

Native层说明

作为UI层与Flutter层的中间媒介,承载了整个通信链路,Native通过JS引擎来加载JSBundle包,并依次注入函数方法来达到 JSCallNative通信的目的;MethodChannel现方案每个Flutter engine里面维护一个Channel实例通过MethodChannle来达到FlutterCallNative以及NativeCallFlutter的目的,这种方式目前能满足现有业务需求,后期将对engine改造成单例化,提供性能和内存;并增加异常监控处理等等。核心模式flutter ⇋ native ⇋ js 环境以及通道核心流程

image-20200330010008568
Flutter层

主要处理以下几个方面:

1、JS数据转换Dart:JavaScript定义了与Flutter一一对应的基础组件,并依据 Flutter 中对 Widget 注册的所有拦截函数,这样可以达到UI组件与Dart Widget 之间进行组件的互相转换;

2、事件触发与传递:每个JS方法、组件、页面均会生成唯一id,并通过事件函数来传递到Flutter层,以此来区分事件类型与执行策略并达到事件传递;

3、JS与Flutter组件的更新:更新分为两部分,JS层与Flutter层,JS层主要是参考React diff策略来拿到需要更新的组件并通过消息通道传递给Flutter,Flutter层再根据该数据【更新的页面名称、更新节点 id 以及更新节点的 json 数据】来生成新的页面widget,Flutter通过调用setState方法来做diff组件更新;

4、页面渲染与生命周期管理:对于当完成所有链路的数据转换后就会拿到modelTree & widgetTree,modelTree会持有并缓存widgetTree,最终构建一个Widget页面并渲染显示;

image-20200330002603526

接入指南

如果你要在项目中使用 Thresh 提供的 flutter 动态化能力,可以参考以下步骤快速创建一个Thresh 项目。

一个 Thresh项目由 几个部分构成,分别是:JS业务代码、thresh-flutter 、thresh-js与 native 集成。本文将会对 JS 和 Flutter 端的接入以及本地调试做出说明。

环境准备

Android 端环境 :Android Studio iOS 端:XCode

JS:VSCode + node + npm/yarn

Flutter SDK:flutter1.9.1-hotfix.6

仓库说明

仓库名称地址开发语言备注
宿主thresh插件工程git@code.amh-group.com:Wireless/thresh.gitjava / oc / dartnative/dart
ThreshDemo工程git@code.amh-group.com:Wireless/dynamic_flutter_demo.gitts/jsJS Demo 工程
dynamic_flutter_js_frameworkgit@code.amh-group.com:Wireless/dynamic_flutter_js_framework.gitjs核心JS代码
dynamic_flutter_js_templategit@code.amh-group.com:Wireless/dynamic_flutter_js_lib.gitjsJS 脚手架模板项目
dynamic_flutter_js_cligit@code.amh-group.com:Wireless/dynamic_flutter_js_cli.gitjsJS 脚手架

Demo工程准备

1、手动创建JS空项目工程 【演示示例工程: dynamic_flutter_demo】

和大部分框架一样,Thresh 的 js 端框架也有自己的脚手架工具,通过脚手架工具可以便捷快速地创建一个 Thresh js 工程,具体流程如下:(以 yarn 为例,如未安装 yarn,可替换为 npm 的等价命令)

  1. 全局安装脚手架工具:yarn global add thresh-js-cli

  2. 在目标目录中执行 thresh-cli create yourAppName 命令即可创建一个新的,如:thresh-cli create transport,项目创建的同时会自动安装相关依赖。

    TIP

    thresh-cli create 命令创建的项目,其项目名会同时作为 package.json 中的 name 字段,并且该字段会作为项目在宿主工程中的模块名被使用。因此如项目名不是模块名,需要自行修改 package.json 中的 name 字段。

2、启动JS项目

进入项目根目录,

1、执行 yarn install命令,安装依赖文件;【如出现安装失败或其他报错,可尝试删除默认yarn.lock文件并重新执行install命令】

2、执行 yarn dev 命令将会启动项目并进入本地开发模式。

开发模式下会启动本地 http 服务,默认服务端口为 12345,端口号可以在根目录 /webpack/config.js 中修改。对于 js 端相关配置与打包感兴趣可以参考 打包说明

TIP

不建议对默认端口号进行修改,否则也需要同步修改调试宿主工程的相应端口号。

项目启动后无法在浏览器中查看页面,需要在调试宿主工程中进行查看与调试。

如需在实际宿主工程中沙盒调试本地代码,请执行 yarn prod 命令,将会以可运行于实际宿主中的方式启动本地开发模式。两种启动命令对应的 process.env.NODE_ENV 分别为 development 和 production.

调试部分请参考本文 本地调试

image-20200329213237962
3、打开ThreshDemo宿主工程
a、yaml源配置

Flutter 端请在 pubspec.yaml 文件中添加依赖项:

thresh:
    git:
      url: git@code.ymmoa.com:flutter/dynamic_flutter.git
      ref: release_open_source

请参考 调试工具 相关说明。

b、启动flutter run ,会出现以下示例:【由于js服务器未启动或者手机未连接电脑代理导致的】

Thresh Thresh

c、真机开发的话,需将手机代理连上当前js服务器的ip【即电脑的ip】,如使用模拟器 可无需配置代理,代理必须连接成功

d、然后点击刷新按钮即可启动示例工程【如下demo页面】

e、至此,恭喜您项目运行成功!

Demo页面

Thresh Thresh

Thresh Thresh

本地调试 - 宿主沙盒调试模式

本地调试以 Demo 作为说明示例。

运行宿主 AppDemo 工程(目前以 ThreshDemo 项目为宿主 demo 工程),启动后将会直接连接到 http://127.0.0.1:12345/ 加载并执行 js 脚本,从而显示 ThreshDemo 中的页面。页面显示效果如下:

将 ThreshDemo 克隆到本地后,进入根目录,依次执行 yarn install yarn dev 命令即可启动 js demo 的本地调试,服务启动于 http://127.0.0.1:12345/.

依赖项添加完成后,可直接参考 Apis/Flutter 一文进行 Thresh 的接入。

进阶指南

接入案例

以下是本站收集的使用 Thresh 编写的应用,供大家参观学习。 如果你想提交作品,或是要求修改、删除这里列出的应用,请提出 PR

运满满 运满满

运满满司机端 运满满货主端

手机配货神器。 手机配货神器。

应用核心场景【总日均PV超千万,js错误率低于十万分之一】
Thresh

联系我们

如果你在使用过程中遇到问题,或者有好的建议,欢迎给我们提。详细说明请移步 贡献指南

对ThreshDemo有兴趣的小伙伴,可以加群交流 钉钉群:

Thresh

项目作者

开源协议

Thresh遵循MIT开源许可证协议。

致谢

参考以及使用的开源项目

项目名称开源协议说明
https://github.com/flutter/flutter.gitMITdart sdk engine等
6Apache License依赖库
https://github.com/facebook/react-nativeMITjsc加载等
https://github.com/eclipsesource/J2V8.gitEclipse Public License - v 1.0Android v8

Libraries

basic
bridge
bus
controller
dev
dev_panel
dev_tools
dynamic_app
dynamic_builder
dynamic_model
dynamic_page
dynamic_proxy
dynamic_widget
global_def
main
main
panel_content
panel_content_item
render
thresh
MIT License [...]
util
util
widget_app_bar
widget_basic
widget_bottom_sheet
widget_button
widget_checkbox
widget_container
widget_icon
widget_image
widget_input
widget_list_view
widget_native_view
widget_nest_scroll_view
widget_page
widget_radio
widget_refresh
widget_scroll_view
widget_spin
widget_swipe_actions_view
widget_swiper_view
widget_text
widget_toast