共计 3244 个字符,预计需要花费 9 分钟才能阅读完成。
一、概述
大家都晓得,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 办法进行了提取,对于这种办法嵌套的布局构建代码,咱们该怎么解决呢。
大抵的应答办法有几种:
- 在框架层面做限度,不反对这种写法;
- 解析时提取嵌套办法返回的 Widget 内容,在开发时反对办法嵌套,理论生成 DSL 时变成纯 Widget 嵌套形式;
- 缓存嵌套的 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 生成流程的一副图来总结一下具体的流程。