背景

在端上为了提升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.千人千面

根据不同的人群,来动态渲染不一样的界面。

总结

本文介绍动态化方案的渲染部分。该方案都在初探阶段,还有很多需要完善,后续会继续扩展和修改,等达到开源标准后,会考虑开源。动态方案是一个后端前端一体的方案,需要一整套工具配合,后续会有文章继续介绍整体的动态化方案。

原文链接
本文为云栖社区原创内容,未经允许不得转载。

做了2个多月的设计和编码,我梳理了Flutter动态化的方案对比及最佳实现的更多相关文章

  1. [硬件项目] 2、汽车倒车雷达设计——基于专用倒车雷达芯片GM3101的设计方案与采用CX20106A红外线检测芯片方案对比

    前言 尽管每辆汽车都有后视镜,但不可避免地都存在一个后视镜的盲区,倒车雷达则可一定程度帮助驾驶员扫除视野死角和视线模糊的缺陷,提高驾驶安全性.上一节已经分析清倒车雷达的语音模块(上一节),本节将深入分 ...

  2. 【原创】纯OO:从设计到编码写一个FlappyBird (一)

    说起来,自学计算机也有2年多的时间了,自己还没有从设计到编码,完完整整的设计一个基于面向对象的软件的经历..囧 于是,就有了这个系列.首先选用的语言是Java,没别的原因,HeadFirst设计模式是 ...

  3. CS5265替代LT8711设计TYPEC转HDMI 4K高清投屏方案|LT8711龙迅替代方案

    龙迅LT8711是一款Type-C/DP1.2 to HDMI2.0方案芯片.LT8711HE是一款高性能Type-C/DP1.2至HDMI2.0转换器,设计用于将USB typec或DP1.2源连接 ...

  4. 1. 做node项目 (第二个月)

    工作栈: Node + Express + Mongoose +  Mongodb + Vuejs 主要做了 mongodb的 curd , 因为以前做 PHP + MySql 所以基本大同小异. n ...

  5. 如何通过 Vue+Webpack 来做通用的前端组件化架构设计

    目录:   1. 架构选型     2. 架构目录介绍     3. 架构说明     4. 招聘消息 目前如果要说比较流行的前端架构哪家强,屈指可数:reactjs.angularjs.emberj ...

  6. 【Android UI】如何做一个纯粹的Android app UI 设计

    原文:http://android.eoe.cn/topic/summary 许多开发者会在多个平台上发布应用.如果您打算为 Android 开发应用,请记住在不同的平台需要遵守不同的要求和惯例.在某 ...

  7. 引擎设计跟踪(九.14.2d) [翻译] shader的跨平台方案之2014

    Origin: http://aras-p.info/blog/2014/03/28/cross-platform-shaders-in-2014/ 简译 translation: 作者在2012年写 ...

  8. AngularJS中如何对Controller与Service进行分层设计与编码

    初学者的Controller 在我们当接触NG后,如需要通过访问远程的API获取一系列的数据进行显示,通常我的Controller代码会写成下面的样子: angular.module('demo') ...

  9. 后端开发实践系列之二——领域驱动设计(DDD)编码实践

    Martin Fowler在<企业应用架构模式>一书中写道: I found this(business logic) a curious term because there are f ...

随机推荐

  1. vue定义全局组件

    <!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>& ...

  2. URI ,URL 和 URN

    URI : 统一资源标识符,用来唯一标识互联网资源,包括URL和URN URL:统一资源定位器 包含: 协议,域名,端口,路由,参数,hash https://i.cnblogs.com/EditPo ...

  3. C++ struct结构体定义构造函数和析构函数,构造函数参数从VS2017平台转换到Qt5平台下构建出错,采用字符集转换函数将string类型转换为wstring,构建仍然出错!

    调试win硬件驱动,需要利用VS编译的win驱动构建自己的Qt5GUI程序: 其中部分win驱动源码如下 device_file::device_file(const std::string& ...

  4. Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作

    写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...

  5. mysql5.7忽略大小写问题

    mysql5.7忽略大小写问题 1.1 前言 新安装mysql5.7版本后,linux环境下默认是大小写敏感的. 1.2 忽略大小写敏感步骤 进入mysql配置文件:         vi   /et ...

  6. CentOS6 网络设置

    由于CentOS 6默认开启了arpcheck所以在配置网卡的时候需要关闭,否则导致网络服务启动失败 [root@Mysql ~]# vi /etc/sysconfig/network-scripts ...

  7. python制作词云

    需要模块wordcloud,pip install wordcloud安装即可.代码: , #边距background_color='black',#指定背景颜色font_path='simhei.t ...

  8. 企业IT管理员IE11升级指南【8】—— Win7 IE8和Win7 IE11对比

    企业IT管理员IE11升级指南 系列: [1]—— Internet Explorer 11增强保护模式 (EPM) 介绍 [2]—— Internet Explorer 11 对Adobe Flas ...

  9. [Swift]LeetCode572. 另一个树的子树 | Subtree of Another Tree

    Given two non-empty binary trees s and t, check whether tree t has exactly the same structure and no ...

  10. [Swift]LeetCode698. 划分为k个相等的子集 | Partition to K Equal Sum Subsets

    Given an array of integers nums and a positive integer k, find whether it's possible to divide this ...