Flutter语法检测及原理剖析-Fair语法检测实践

简介: Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。Fair 是 58 技术开源的一个 Flutter 动态化的框架,能够实现UI和逻辑的动态化。

前言

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。

Fair是58技术开源的一个Flutter动态化的框架,能够实现UI和逻辑的动态化。
image.png

image.png

开发者在使用Fair开发过程中存在一些痛点,比如可能会出现使用语法糖不正确或者存在不支持的语法糖问题,所以我们需要一个配套插件去提示用户使用Fair语法糖。

一、Flutter语法检测机制

在IDE中,Flutter语法检测机制是依赖Dart/Flutter插件实现的,即我们在开发Flutter前需要下载的Dart/Flutter插件。我们需要通过插件去提供Flutter的开发环境,同时插件也能提供语法检测功能。而插件其中的一个核心功能就是Analysis Server。

Analysis Server是什么?

Analysis Server是Dart SDK提供的一个Dart/Flutter语法分析服务,主要功能包括语法静态分析、代码提示、代码补全等。我们常用的Dart/Flutter IDE如intellij、Android Studio和VS Code都是通过安装Dart插件实现Dart开发环境的配置。

Dart/Flutter插件语法检测的工作流程

以Android Studio为例(因为对应插件的语言是Java,比较好阅读和理解),语法检测核心是Analysis Server,每当用户代码有改动的时候,就会通过Socket的方式同步给Analysis Server,当Analysis Server分析结束后也会将分析结果返回来,Dart插件则就是根据Analysis Server返的事件类型去做不同的处理,最终将结果刷新在用户IDE界面上。流程图如下:

image.png

Dart插件中Analysis Server的启动流程

当用户配置完Dart SDK路径后,插件会获取到配置路径,在插件启动的时候会启动一个进程去执行Dart SDK里的dart可执行文件,同时也会获取到Dart SDK目录下的"/bin/snapshots/analysis_server.dart.snapshot"文件,并将其路径作为vmArguments。我们可以看一小段代码:


//获取配置的SDK路径
mySdkHome = sdk.getHomePath();

//找到dart可执行文件的路径
final String runtimePath = FileUtil.toSystemDependentName(DartSdkUtil.getDartExePath(sdk));

//找到analysis_server.dart.snapshot文件路径
String analysisServerPath = FileUtil.toSystemDependentName(mySdkHome + "/bin/snapshots/analysis_server.dart.snapshot");

//拼凑vmArguments
String firstArgument = useDartLangServerCall ? "language-server" : analysisServerPath;

//创建Socket
myServerSocket =
        new StdioServerSocket(runtimePath, StringUtil.split(vmArgsRaw, " "), firstArgument, StringUtil.split(serverArgsRaw, " "), debugStream);

//创建AnalysisServer实现类
final RemoteAnalysisServerImpl startedServer = new RemoteAnalysisServerImpl(myServerSocket);

//这里的具体实现其实就是socket.start(),将socket启动起来
startedServer.start();

Dart插件与Analysis Server交互

Dart插件与Analysis Server交互类型主要分为两种,一种是id,一种是event。每次Dart插件给Analysis Server同步数据时都会生成一个uniqueId并缓存在HashMap,每次Analysis Server返数据的时候也会带上event或id,优先处理event类型数据。

Dart插件同步给Analysis Server数据示例:

request: {
  "id": String
  "method": "analysis.setAnalysisRoots"
  "params": {
    "included": List<FilePath>
    "excluded": List<FilePath>
    "packageRoots": optional Map<FilePath, FilePath>
  }
}

上述说到Dart插件每次给Analysis Server同步数据都会生成一个uniqueId,这里的生成规则就是通过具备原子属性的AtomicInteger的getAndIncrement()方法每同步一次数据+1。Dart插件对应Analysis Server每种事件实现一个Consumer或Processor(如果是event类型数据则是Processor,result事件类型的话就是Consumer)。

语法检测插件的挂载流程

插件是有一个挂载过程的,插件的挂载流程如下图所示:
image.png

小结一下:

  1. YamlParaser解析analysis_options.yaml文件中analyzer#plugins节点,获取到一个pluginNameList
  2. 根据pluginNameList遍历通过Plugin Locator获取到每个plugin的analyzer_plugin目录路径,即pluginPath
  3. 将pluginPath目录路径文件copy到系统的.dartServer/plugin_manager/unique Directory目录下,其中unique Directory是通过对pluginPath进行md5操作生成的串
  4. 执行pluginPath下/bin/plugin.dart的main方法

二、Fair语法检测插件的实现原理

当我们掌握了前面的前置知识,再去开发语法插件就简单许多了。

Fair语法检测插件的实现机制

Dart插件是基于Analysis Server实现了Dart语法的检测,Fair语法检测插件则是对Dart插件语法检测功能的补充和扩展,实现对Fair语法糖的检测,Fair语法插件本质页是一个Dart语法检测插件,可以通过配置pubspec.yaml和analysis_options.yaml来使用。
image.png

语法插件的开发流程

主要可以分为以下几步:

  1. 首先创建一个类去继承ServerPlugin,并实现抽象属性/方法。
  2. 然后跟lib同级创建tools/analyzer_plugin目录,内容如图所示:
    image.png
  3. 最后在plugin.dart文件中注册步骤1中实现的ServerPlugin。
    ```Dart
    //示例
    void main(List args, SendPort sendPort) {
    ServerPluginStarter(FairPlugin(PhysicalResourceProvider.INSTANCE)).start(sendPort);
    }


### 核心实现
Fair语法检测插件的核心就是抽象语法树遍历,通过对抽象语法树的遍历,获取到每个子节点,然后插入自定义语法检测逻辑。

#### 1.创建AnalysisDriver
createAnalysisDriver()是ServerPlugin的一个抽象方法,开发者实现这个方法创建一个AnalysisDriver,然后我们可以通过AnalysisDriver的results数据流去监听AnalysisResult的返回,其中这个AnalysisResult就是Analysis Server对文件的分析结果。示例代码如下所示:
```Dart
@override
AnalysisDriverGeneric createAnalysisDriver(plugin.ContextRoot contextRoot) {

    //获取contextRoot
    final contextRoots =
        ContextLocator(resourceProvider: resourceProvider).locateRoots(...);

    //获取ContextBuilder
    final contextBuilder =
        ContextBuilderImpl(resourceProvider: resourceProvider);

    //获取Analysis Context
    final context = contextBuilder.createContext(
        contextRoot: contextRoots.first, byteStore: byteStore);


    //获取Analysis Driver
    final dartDriver = context.driver;

    //监听分析结果
    dartDriver.results.listen((analysisResult) {_processResult(...);});
    return dartDriver;
}

2.感知代码变化

ServerPlugin提供contentChanged方法,我们只需要收到回调的时候将内容有改变的文件路径添加到AnalysisDriver即可

@override
void contentChanged(String path) {
  super.driverForPath(path)?.addFile(path);
}

3.利用AOP思想对AnalysisResult处理

Dart的抽象语法树遍历是通过访问者模式实现的,我们可以在我们想要处理的节点插入一个自定义的Visitor对其及其子节点实现访问。其中我们可以从AnalysisResult拿到编译单元(CompilationUnit),每个编译单元就是一棵抽象语法树,我们可以通过其accept()方法插入一个Visitor。

4.@FairPatch注解识别实现逻辑

我们了解到Dart抽象语法树是通过访问者模式实现的,就能够很简单地去实现自定义的逻辑了。我们要实现@FairPatch注解的识别,我们只需要在遍历Annotation节点时,判断一下annotation name是我们想要的FairPatch即可。示例代码如下:

class _FairVisitor extends RecursiveAstVisitor<void> {

  @override
  void visitAnnotation(Annotation node) {
    node.visitChildren(this);
    if (node.name.name == \'FairPatch\') {
      //...
    }
  }
}

5.if语法检测

因为if语法检测是需要前置条件的,首先得是用@FairPatch标注的类,其次还需要是在build()方法下的if才进行检测。

  • 首先是build()方法检测,只需要在method deceleration的时候进行一下方法过滤即可。
    ```
    @override
    void visitMethodDeclaration(MethodDeclaration node) {
    if (node.name.name == \'build\') {
      //...
    
    }
    }
- 其次是if语法检测,稍微复杂一点,if分为IfStatement和IfElement,IfStatement指代的是外层的if-else语句,IfElement指代的是嵌套在其他语法里的if-else语句,如果想要全覆盖if语法,则需要两者都实现。



![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ade0fa8c60f14716afa2fcae210cec17~tplv-k3u1fbpfcp-zoom-1.image)
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8ee9357413904faa8f5f70a49e98191d~tplv-k3u1fbpfcp-zoom-1.image)
上述两张图分别对应IfStatement和IfElement具体含义。

#### 6.Fair的Sugar.if语法糖分类实现
在Fair中常用的Sugar.if相关语法糖有Sugar.ifEqual、Sugar.ifEqualBool和Sugar.ifRange

- Sugar.ifRange
```Dart
static K ifRange<T, K>(
    T actual,
    List<T> expect, {
    required K trueValue,
    required K falseValue,
  }) =>
      expect.contains(actual) ? trueValue : falseValue;
  • Sugar.ifEqual

    static K ifEqual<T, K>(
      T actual,
      T expect, {
      required K trueValue,
      required K falseValue,
    }) =>
        expect == actual ? trueValue : falseValue;
    
  • Sugar.ifEqualBool

    static K ifEqualBool<T, K>(
      bool state, {
      required K trueValue,
      required K falseValue,
    }) =>
        state ? trueValue : falseValue;
    

使用Fair语法糖能够加快Fair的编译,所以我们都更推荐能使用语法糖就使用语法糖,我们要实现对语法糖分类,其实就是对if的condition进行区分,其中ifRange稍微复杂一点,则我们再讲下如何实现,其他也同理:

//ifRange有个前置条件就是如参expect是一个List,所以再检测的时候只需要判断用户在调用list.contains(xx)即可

//先判断用户是在调用contains()方法
  @override
  void visitMethodDeclaration(MethodDeclaration node) {
    if (node.name.name == \'contains\') {
      //...
    }
  }


//其次再判断调用contains方法的对象是List
  @override
  void visitMethodInvocation(MethodInvocation node) {
    super.visitMethodInvocation(node);

    //判断staticType即可
    _result = node.target?.staticType?.isDartCoreList ?? false;
    if (_result) {
      _target = node.target;
      _actual = node.argumentList.arguments.first;
    }
  }

三、Fair Plugin实现效果展示

  • Android Studio实现对build()方法下if语法块的检测效果
  1. build方法下if的代码检测,及提示引导信息
    image.png
  2. 点击more action 或者 AS代码提示快捷键
    image.png
  3. 根据提示点击替换
    image.png
相关文章
|
1月前
|
开发者 容器
Flutter&鸿蒙next 布局架构原理详解
本文详细介绍了 Flutter 中的主要布局方式,包括 Row、Column、Stack、Container、ListView 和 GridView 等布局组件的架构原理及使用场景。通过了解这些布局 Widget 的基本概念、关键属性和布局原理,开发者可以更高效地构建复杂的用户界面。此外,文章还提供了布局优化技巧,帮助提升应用性能。
104 4
|
1月前
|
Dart UED 开发者
flutter鸿蒙版本通过底部导航栏的实现熟悉架构及语法
这篇博客详细解析了一个 Flutter 应用的完整代码,实现了带有底部导航栏的功能,允许用户在不同页面之间切换。通过逐行讲解,帮助读者理解 Flutter 的结构、状态管理和组件交互。代码涵盖了从引入包、创建主入口、定义无状态和有状态组件,到构建用户界面的全过程。希望对 Flutter 开发者有所帮助。
153 3
|
1月前
|
存储 Dart 前端开发
flutter鸿蒙版本mvvm架构思想原理
在Flutter中实现MVVM架构,旨在将UI与业务逻辑分离,提升代码可维护性和可读性。本文介绍了MVVM的整体架构,包括Model、View和ViewModel的职责,以及各文件的详细实现。通过`main.dart`、`CounterViewModel.dart`、`MyHomePage.dart`和`Model.dart`的具体代码,展示了如何使用Provider进行状态管理,实现数据绑定和响应式设计。MVVM架构的分离关注点、数据绑定和可维护性特点,使得开发更加高效和整洁。
163 3
|
2月前
动画控制器在 Flutter 中的工作原理
【10月更文挑战第18天】总的来说,动画控制器 `AnimationController` 在 Flutter 中起着关键的作用,它通过控制动画的数值、速度、节奏和状态,实现了丰富多彩的动画效果。理解它的工作原理对于我们在 Flutter 中创建各种精彩的动画是非常重要的。
|
6月前
|
存储 开发框架 JavaScript
深入探讨Flutter中动态UI构建的原理、方法以及数据驱动视图的实现技巧
【6月更文挑战第11天】Flutter是高效的跨平台移动开发框架,以其热重载、高性能渲染和丰富组件库著称。本文探讨了Flutter中动态UI构建原理与数据驱动视图的实现。动态UI基于Widget树模型,状态变化触发UI更新。状态管理是关键,Flutter提供StatefulWidget、Provider、Redux等方式。使用ListView等可滚动组件和StreamBuilder等流式组件实现数据驱动视图的自动更新。响应式布局确保UI在不同设备上的适应性。Flutter为开发者构建动态、用户友好的界面提供了强大支持。
113 2
|
2月前
|
容器
Flutter&鸿蒙next 布局架构原理详解
Flutter&鸿蒙next 布局架构原理详解
|
2月前
|
Dart UED 索引
flutter鸿蒙版本通过底部导航栏的实现熟悉架构及语法
flutter鸿蒙版本通过底部导航栏的实现熟悉架构及语法
33 2
|
4月前
|
SQL 分布式计算 大数据
Flutter技术实践问题之Flutter应用过程中的基础建设如何解决
Flutter技术实践问题之Flutter应用过程中的基础建设如何解决
34 10
|
4月前
|
新零售 前端开发 小程序
Flutter技术实践问题之基于Flutter的Canvas的应用优势如何解决
Flutter技术实践问题之基于Flutter的Canvas的应用优势如何解决
41 2
|
4月前
|
Web App开发 新零售 前端开发
Flutter技术实践问题之阿里集团内Flutter体系化建设如何解决
Flutter技术实践问题之阿里集团内Flutter体系化建设如何解决
47 1
下一篇
DataWorks