共计 4232 个字符,预计需要花费 11 分钟才能阅读完成。
背景
在端上为了提升 App 的灵活性,快速解决万变的业务需求,开发者们探索了多种解决方案,如 PhoneGap,React Native ,Weex 等,但在 Flutter 生态还没有好的解决方案。未来闲鱼都会基于 Flutter 来跨端开发,如果突破发版周期,在不发版的情况下,完成业务需求,同时能兼容性能体验,无疑是更快的响应了业务需求。因此我们需要探索在 Flutter 生态下的动态化。
方案选择
借鉴 Android 和 Ios 上的动态性方案,我们也思考了多种 Flutter 动态性方案。
1. 下载替换 Flutter 编译产物
下载新的 Flutter 编译产物,替换 App 安装目录下的编译产物,来实现动态化,这在 Android 端是可行的,但在 Ios 端不可行。我们需要双端一体的解决方案,所以这不是最好选择。
2. 类似 React Native 框架
我们先来看看 React Native 的架构
React Native 要转为 android(ios)的原生组件,再进行渲染。用 React Native 的设计思路,把 XML DSL 转为 Flutter 的原子 widget 组件,让 Flutter 来渲染。技术上说是可行的,但这个成本很大,这会是一个庞大的工程,从投入产出比看,不是很好的选择
3. 页面动态组件框架
由粗粒度的 Widget 组件动态拼装出页面,Native 端已经有很多成熟的框架,如天猫的 Tangram,淘宝的 DinamicX,它在性能、动态性,开发周期上取得较好平衡。关键它能满足大部分的动态性需求,能解决问题。
三种方案的比较图表如:
根据实际动态性需求,从两端一致性,和性能,成本,动态性考虑,我们选择一个折中方案,页面动态组件的设计思路是一个不错的选择。
页面动态组件框架
在 Flutter 上使用粗力度的组件动态拼装来构建页面,需要一整套的前后端服务和工具。本文我们重点介绍前端界面渲染引擎过程。
语法树的选择
Native 端的 Tangram,DinamicX 等框架他们有个共同点,都是 Xml 或者 Html 做为 DSL。但是 Flutter 是 React Style 语法。他自己的语法已经能很好的表达页面。无需要自定义的 Xml 语法,自定义的逻辑表达式。用 Flutter 源码做为 DSL 能大大减轻开发,测试过程,不需要额外的工具支持。所以选择了 Flutter 源码作为 DSL,来实现动态化。
如何解析 DSL
Flutter 源码做为 DSL,那我们需要对源码进行很好的解析和分析。Flutter analyzer 给了我们一些思路,Flutter analyzer 是一个代码风格检测工具。它使用 package:analyzer 来解析 dart 源码,拿到 ASTNode。
看下 Flutter analyze 源码结构,它使用了 dart sdk 里面的 package:analyzer
dart-sdk:
analysis_server:
analysis_server.dart
handleRequest(Request request)
analyzer:
parseCompilationUnit()
parseDartFile
parseDirectives
Flutter analyze 解析源码得到 ASTNode 过程。
插件或者命令对 analysis server 发起请求,请求中带需要分析的文件 path,和分析的类型,analysis_server 经过使用 package:analyzer 获取 commilationUnit(ASTNode),再对 astNode,经过 computer 分析,返回一个分析结果 list。
同样我们也可以把使用 package:analyzer 把源文件转换为 commilationUnit(ASTNode),ASTNode 是一个抽象语法树,抽象语法树(abstract syntax tree 或者缩写为 AST)是源代码的抽象语法结构的树状表现形式.
所有利用抽象语法树能很好的解析 dart 源码。
解析渲染引擎
下面重点介绍渲染模块
架构图:
1. 源码解析过程
1.AST 树的结构
如下面这段 Flutter 组件源码:
import ‘package:flutter/material.dart’;
class FollowedTopicCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Container(
padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 0.0),
child: new InkWell(
child: new Center(
child: const Text(‘Plugin example app’),
),
onTap: () {},
),
);
}
}
它的 AST 结构:
从 AST 结构看,他是有规律的.
2.AST 到 widget Node
我们拿到了 ASTNode,但 ASTNode 和 widget node tree 完全是两个不一样的概念,需要递归 ASTNode 转化为 widget node tree.
widget Node 需要的元素
用 Name 来记录是什么类型的 widget
widget 的 arguments 放在 map 里面
widget 的 literals 放在 list 里面
widget 的 children 放在 lsit 里面
widget 的触发事件 函数 map 里面
widget node 加 fromjson,tojson 方法
可以在递归 astNode tree 时候,识别 InstanceCreationExpression 来创建一个 widget node。
2. 组件数据渲染
框架 sdk 中注册支持的组件,组件包括:
a. 原子组件:Flutter sdk 中的 Flutter 的 widget
b. 本地组件:本地写好到一个大颗粒的组件,卡片 widget 组件
c. 逻辑组件:本地包装了逻辑的 widget 组件
d. 动态组件:通过源码 dsl 动态渲染的 widget
具体代码如下:
const Map<String, CreateDynamicApi> allWidget = <String,
CreateDynamicApi>{
‘Container’: wrapContainer,
………….
}
static Widget wrapContainer(Map<String, dynamic> pars) {
return new Container(
padding: pars[‘padding’],
color: pars[‘color’],
child: pars[‘child’],
decoration: pars[‘decoration’],
width: pars[‘width’],
height: pars[‘height’],
alignment: pars[‘alignment’]
);
}
一般我们通过网络请求拿到的数据是一个 map。比如源码中写了这么一个 ‘${data.urls[1]}’AST 解析时候,拿到这么一个 string,或者 AST 表达式,通过解析它,肯定能从 map 中拿到对应的值。
3. 逻辑和事件
a. 支持逻辑
Flutter 概念万物都是 widget,可以把表达式,逻辑封装成一个自定义 widget。如果在源码里面写了 if else,变量等,会加重 sdk 解析的过程。所以把逻辑封装到 widget 中。这些逻辑 widget,当作组件当成框架组件。
b. 支持事件
把页面跳转,弹框,等服务,注册在 sdk 里面。约定使用者仅限 sdk 的服务。
4. 规则和检测工具
a. 检测规则
需要对源码的格式制定规则。比如不支持 直接写 if else,需要使用逻辑 wiget 组件来代替 if else 语句。如果不制定规则,那 ast Node 到 widget node 的解析过程会很复杂。理论上都可以解析,只要解析 sdk 够强大。制定规则,可以减轻 sdk 的解析逻辑。
b. 工具检测
用工具来检测源码是否符合制定的规则,以保证所有的源码都能解析出来。
性能和效果
帧率大于 50fps,体验上看比 weex 相同功能的页面更加流畅,Samsung galaxy s8 上,感觉不出组件是通过动态渲染的.
数据结构
服务端请求到的数据,我们可以约定一种格式如下:
class DataModel {
Map<dynamic, dynamic> data;
String type;
}
每个 page 都是由组件组成的,每个组件的数据都是 DataModel 来渲染。根据 type 来找到对应的模版,模版 +data,渲染出界面。
动态模版管理模块
我们把 Widget Node Tree 转换为一个组件 Json 模版,它需要一套管理平台,来支持版本控制,动态下载,升级,回滚,更新等。
框架的边界
该框架是通过组件的组装,组件布局动态变更,页面布局动态变更来实现动态化。所以它适合运营变化较快的首页,详情,订单,我的等页面。一些复杂的逻辑需要封装在组件里面,把组件内置到框架中,当作本地组件。框架侧重于动态组件的组装,而引擎对于源码复杂的逻辑表达式的解析是弱化的。
后续拓展
1. 和 UI 自动化的结合
UI 自动化,前面已经有文章介绍。UI 自动化工具生成组件,再组件转为模版,动态下发,来快速解决运营需求。
2. 国际化的支持
App 在不同国家会有不同的功能,我们可以根据区域,来动态拼装我们的页面。
3. 千人千面
根据不同的人群,来动态渲染不一样的界面。
总结
本文介绍动态化方案的渲染部分。该方案都在初探阶段,还有很多需要完善,后续会继续扩展和修改,等达到开源标准后,会考虑开源。动态方案是一个后端前端一体的方案,需要一整套工具配合,后续会有文章继续介绍整体的动态化方案。敬请关注闲鱼技术公共账号,也邀请您加入闲鱼一起探索有意思的技术。
参考资料:
Static Analysis:
https://www.dartlang.org/guides/language/analysis-optionshttps://www.dartlang.org/tools/analyzer
dart analyzer :
https://pub.dartlang.org/packages/analyzerhttps://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli#dartanalyzer
dartdevc:
https://webdev.dartlang.org/tools/dartdevc
本文作者:闲鱼技术 - 石磬阅读原文
本文为云栖社区原创内容,未经允许不得转载。