一、概述

大家都晓得,Flutter在release环境是以AOT模式运行的,这就决定了咱们要做动态化的话无奈简略的通过动静下发dart代码执行的。依据Fair团队的后期调研,咱们对布局动态化和逻辑动态化的实现采纳了两套不同的实现计划,对于布局局部,咱们在解析dart源文件之后生成DSL产物下发,而后在端上解析DSL构建布局的形式,逻辑动态化的局部,咱们采纳的是dart源码转js下发的形式。

整个动态化流程大抵如下:

二、整体流程概述

详述具体流程之前,咱们先来看看整体的流程,而后再去解说各个流程的原理细节。

整个流程大抵分为两局部:

  • 通过fair_ast_gen将源码解析并生成AstMap;
  • 通过fair_dsl_gen将AstMap转换成咱们须要的Fair DSL。

这里波及到两个概念,大家须要先理解一下

  • AST 全称是Abstract Syntax Tree,中文名为形象语法树。
  • DSL 全称是Domain Specific Language,中文名为畛域特定语言。

三、AST解析

3.1 源码解析

要把dart源码转换成咱们须要的DSL,首先要对dart源码进行形象语法分析,这里是整个转换过程的第一个关键点,甚至能够说是整个DSL生成的根底。好在dart 官网提供了解析工具包analyzer,这为咱们整个的dsl生成工作大大加重了工作量。

analyzer包的utilities类提供了parseFile函数,这个函数返回的CompilationUnit,实际上是一个编译单元,继承自AstNode,正是咱们前面AST解析的入口。

3.2 AST解析

后面咱们提到,AST是一种与编程语言无关的形象语法树,对于这块的概念不是太熟同学,咱们还是先看一个小例子。比方上面这段代码:

那如果转换成AST的话,差不多是上面这样的:

理论转换产物很长,咱们只截取一部分,不过能够大抵看出AST的整个构造,能够看出,整个AST实际上是对源码的一个树状构造形容,整个构造里蕴含很多节点对象,每个节点上面又蕴含各种树形和子节点。

咱们将100+的语法节点分类形象为标识符、字面量、表达式、语法块,其它五大类,30+种的罕用节点,同时剥离了与Fair产物解析无关的信息,只保留原始node中的要害信息,使得节点解析更加清晰。

后面咱们说到analyzer的praseFile办法解析完dart源文件后返回了AstNode实例,咱们对AstNode的剖析次要由该类的accept办法提供,这里用到了访问者设计模式。

accept办法接管的参数类型是AstVisitor,这是一个接口,咱们正是通过这个接口的一系列办法实现对上述实例中各个节点的遍历的。

正如下面的例子看到的,原始AstNode数据量很大,哪怕是一个简略的Demo,解析进去的AST实际上是蕴含很多节点信息的,所以咱们并不通过实现AstVisitor接口来实现所有节点类型的拜访。analayzer包提供了SimpleAstVisitor,咱们能够继承这个类来自定义Visitor,按须要抉择咱们反对的节点去实现办法就能够了。相干代码如下:

最初返回的是一个Map类型的Ast节点树,感兴趣的同学能够间接通过源码理解细节

四、DSL生成

4.1 从AST到DSL生成流程

以下是AST到DSL的整个生成的过程:

有了第一步生成的AST语法树Map产物,再依据AST Map来生成DSL就比拟好了解了。在DSL的生成流程当中,次要是对节点的遍历,而后针对办法,表达式和变量的解决。

因为DSL次要解决的是布局动态化的局部,实际上对于一个Wiget的解析解决,咱们次要是针对build办法中return的内容局部进行了提取并生成DSL(此处的methodMap,咱们先放到前面再解说,并不影响对主流程的了解)。相应代码如下所示:

对于DSL的格局,在debug环境,为了不便调试与直观的发现问题,咱们的产物采纳json格局,在线上环境,处于产物大小的管制及解析速度的思考,咱们下发产物格局改为flatbuffer(google推出的一种高性能,小体积的序列化计划)。

4.2 布局动态化原理

实际上有了analyzer作为根底,DSL的生成在技术上的难点并不大,可是咱们的DSL的构造应该是什么样的,这取决于fair在运行时怎么对DSL进行还原,毕竟咱们的DSL生成最初是为了动静还原成Wiget树并渲染的。针对这部分内容,咱们做个大抵的理解,这样能更好的了解上面的内容。

咱们晓得,Flutter因为某些起因,对dart:mirror包进行了移除,这就决定了咱们没法通过反射对DSL进行布局构建还原,不过Flutter还有一个万能的办法Function.apply。

这个办法,是咱们动态化计划中的第二个重要办法,是Widget树还原的根底。

端上接管到下发的DSL后,只能解析到对应的字符串String类型,咱们只需将对应的String映射到对应的办法(此处次要反对构造方法和类静态方法),便能够将对应的DSL还原并构建Widget树。在fair当中,大抵是这样的。此处咱们写了一个工具库,以不便对flutter widget映射关系的主动生成。

4.3 DSL构造

理解了fair对DSL解析执行的大抵原理之后,咱们再来了解DSL的大抵构造就比拟容易了。

下面的className对应的是下面映射关系中的key,na和pa对应的是可选命名参数和位参数,此处咱们须要解释的是下面提到的methodMap。所谓的methodMap,从字面意思上了解,其实就是办法缓存,在这里咱们同样以下面的HelloWord类为例。

能够看到,在咱们的示例中build办法嵌套了一个布局构建办法_buildText()。后面咱们讲到,在布局动态化DSL生成过程中,咱们次要是对build办法进行了提取,对于这种办法嵌套的布局构建代码,咱们该怎么解决呢。

大抵的应答办法有几种:

  1. 在框架层面做限度,不反对这种写法;
  2. 解析时提取嵌套办法返回的Widget内容,在开发时反对办法嵌套,理论生成DSL时变成纯Widget嵌套形式;
  3. 缓存嵌套的Widget构建办法,在运行时解析Json内容后对函数进行实时的替换。

以上形式中,显然1是让人不可承受的,如果要接入动态化框架有这样的限度,恐怕会让应用的开发者望而生畏。至于办法2和办法3,其实大同小异,一个是在解析时替换,一个是在运行时替换,思考到咱们生成DSL尽量不要扭转原有的代码构造,咱们抉择了计划3。这就是为什么咱们的DSL Json中须要有methodMap的起因。

以下面HelloWord类DSL解析后果为例,能够看到methodMap当中实际上缓存的是除build办法外的Widget构建的相干办法。

4.4 变量和表达式的解决

下面咱们次要讲的都是办法的解决,然而对于变量和表达式,比方上面这种:

Text('$_counter')

以及这种:

onPressed: _incrementCounter

这两种类型的变量援用,在AST中实际上对应的不同的类型,然而如果在DSL中咱们还是简略的解决成了字符串,理论在DSL解析时就无奈与一般字符串进行辨别了,这里咱们采取了一种比较简单的形式,在通过增加不同的特殊符号前缀进行了辨别。

例如下面两个示例,解决后如下:

Text('$_counter') => #($_counter)onPressed: _incrementCounter => @(incrementCounter)

而后,在Fair解析反对层面通过正则匹配到不同的表达式类型,来做变量的数据绑定曾经办法的逻辑调用等。

五、总结

整个DSL生成流程细节很多,然而总结下来就是通过analyzert提供的AST解析工具提取并精炼对咱们有用的信息,并且依据咱们的Fair框架须要,组合成抽象化的布局DSL构造信息。最初,咱们借用Flutter动态化框架Fair的设计与思考中的对于DSL生成流程的一副图来总结一下具体的流程。