All in FlutterFlutter Developerflutter

做了2个多月的设计和编码,我梳理了Flutter动态化的方案对比

2018-12-04  本文已影响315人  闲鱼技术

作者:闲鱼技术-石磬

背景

在端上为了提升App的灵活性, 快速解决万变的业务需求,开发者们探索了多种解决方案,如PhoneGap ,React Native ,Weex等,但在Flutter生态还没有好的解决方案。未来闲鱼都会基于Flutter 来跨端开发,如果突破发版周期,在不发版的情况下,完成业务需求,同时能兼容性能体验,无疑是更快的响应了业务需求。因此我们需要探索在Flutter生态下的动态。

方案选择

借鉴Android 和Ios上的动态性方案,我们也思考了多种Flutter动态性方案。

1.下载替换Flutter编译产物

下载新的Flutter编译产物,替换 App 安装目录下的编译产物,来实现动态化,这在Android 端是可行的,但在Ios 端不可行。我们需要双端一体的解决方案,所以这不是最好选择。

2.类似React Native 框架

我们先来看看React Native 的架构

MacDown image

React Native 要转为android(ios) 的原生组件,再进行渲染。
用React Native的设计思路,把XML DSL转为Flutter 的原子widget组件,让Flutter 来渲染。技术上说是可行的,但这个成本很大,这会是一个庞大的工程,从投入产出比看,不是很好的选择

3.页面动态组件框架

由粗粒度的Widget组件动态拼装出页面,Native端已经有很多成熟的框架,如天猫的Tangram,淘宝的DinamicX,它在性能、动态性,开发周期上取得较好平衡。关键它能满足大部分的动态性需求,能解决问题。

三种方案的比较图表如:


MacDown image

根据实际动态性需求,从两端一致性,和性能,成本,动态性考虑,我们选择一个折中方案,页面动态组件的设计思路是一个不错的选择。

页面动态组件框架

在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过程。

MacDown image

插件或者命令对analysis server发起请求,请求中带需要分析的文件path,和分析的类型,analysis_server经过使用 package:analyzer 获取 commilationUnit (ASTNode),再对astNode,经过computer分析,返回一个分析结果list。

同样我们也可以把使用 package:analyzer 把源文件转换为commilationUnit (ASTNode),ASTNode是一个抽象语法树,抽象语法树(abstract syntax tree或者缩写为AST)是源代码的抽象语法结构的树状表现形式.

所有利用抽象语法树能很好的解析dart 源码。

解析渲染引擎

下面重点介绍渲染模块

架构图:

MacDown image

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结构:

MacDown image

从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 node> 里面

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-options
https://www.dartlang.org/tools/analyzer

dart analyzer :

https://pub.dartlang.org/packages/analyzer
https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli#dartanalyzer

dartdevc:

https://webdev.dartlang.org/tools/dartdevc

上一篇 下一篇

猜你喜欢

热点阅读