MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Flutter架构中的框架层与引擎层协同工作原理

2022-06-291.6k 阅读

Flutter架构概述

Flutter的架构主要分为三层,从上层到下层分别是:框架层(Framework)、引擎层(Engine)和嵌入层(Embedder)。本文主要聚焦于框架层与引擎层的协同工作原理。

框架层是Flutter应用开发直接接触的部分,它提供了丰富的UI组件库、基础的布局模型以及处理用户交互、动画等功能的API。例如,我们常用的MaterialCupertino组件库,就是框架层为开发者提供的不同风格的UI组件集合,以适配不同平台的设计语言。

引擎层则负责与底层操作系统进行交互,它处理图形渲染、文本排版、动画处理等核心任务,为框架层提供了一个高效的运行环境。引擎层是用C++编写的,具有较高的性能和效率。

嵌入层则负责将Flutter应用嵌入到原生平台中,不同平台(如Android、iOS等)有各自的嵌入层实现,它主要处理平台相关的一些初始化、生命周期管理等任务。

框架层与引擎层的通信机制

  1. 消息传递机制
    • 在Flutter中,框架层和引擎层之间通过消息传递来进行通信。框架层会将一些需要引擎层执行的任务,如绘制UI、处理动画等,封装成消息发送给引擎层。
    • 例如,当我们在框架层构建一个新的UI组件时,框架层会将该组件的描述信息(如布局、样式、子组件等)以消息的形式发送给引擎层。引擎层接收到消息后,根据这些信息进行相应的渲染操作。
    • 代码示例:
// 假设我们在框架层创建一个简单的文本组件
Text('Hello, Flutter')

当这个Text组件被构建时,框架层会将其相关信息(如文本内容、字体样式等)打包成消息发送给引擎层。引擎层根据这些信息在屏幕上绘制出对应的文本。

  1. 隔离与共享状态
    • 框架层和引擎层运行在不同的隔离环境中。这种隔离有助于提高应用的稳定性和安全性,避免一个层的错误影响到另一个层。
    • 然而,它们之间需要共享一些状态信息,比如应用的当前状态(是否处于活动状态、是否暂停等)。这是通过一些共享的数据结构和机制来实现的。
    • 例如,框架层中的StatefulWidget的状态变化,有时候需要通知引擎层进行重新渲染。框架层会通过特定的通道将状态变化信息传递给引擎层。
class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter App'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

在这个例子中,当点击FloatingActionButton时,_counter状态变化,setState方法会通知框架层状态改变,框架层进而通过消息通知引擎层进行UI的重新渲染。

框架层的工作原理

  1. 组件树的构建与管理
    • 框架层采用组件化的思想,应用由一系列的组件组成,这些组件构成了一颗组件树。
    • 当应用启动时,框架层会从根组件开始构建组件树。例如,在一个常见的Flutter应用中,MaterialApp通常是根组件,它会包含Scaffold等子组件,Scaffold又可能包含AppBarBody等更多子组件。
    • 组件树的构建过程是递归的,每个组件都有一个build方法,用于描述如何构建其子组件。当组件的状态或属性发生变化时,框架层会根据变化重新构建组件树的相关部分。
// 简单的组件树示例
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: Text('Component Tree Example'),
      ),
      body: Center(
        child: Text('This is a simple component tree'),
      ),
    ),
  );
}

在这个代码片段中,MaterialApp是根组件,Scaffold是它的子组件,AppBar和包含TextCenter组件又是Scaffold的子组件,形成了一个简单的组件树结构。

  1. 布局系统
    • 框架层的布局系统基于盒模型,每个组件都被视为一个矩形的盒子。
    • 布局过程分为两个阶段:测量(measure)和布局(layout)。在测量阶段,每个组件会根据其父组件的约束(如最大宽度、最大高度等)来确定自己的大小。在布局阶段,组件会根据测量得到的大小和父组件的布局规则来确定自己在屏幕上的位置。
    • 例如,Row组件会将其子组件水平排列,它在测量阶段会询问每个子组件的期望大小,然后根据这些信息确定自身的大小,在布局阶段会将子组件按照水平方向依次放置。
Row(
  children: [
    Container(
      width: 100,
      height: 100,
      color: Colors.red,
    ),
    Container(
      width: 100,
      height: 100,
      color: Colors.blue,
    ),
  ],
)

在这个Row布局中,两个Container子组件会水平排列,Row会根据子组件的大小和自身的布局规则进行测量和布局操作。

  1. 用户交互处理
    • 框架层负责处理用户与UI的交互,如点击、滑动等事件。
    • 当用户在屏幕上进行操作时,框架层会捕获这些事件,并将其传递给相应的组件进行处理。组件可以通过注册回调函数来响应这些事件。
    • 例如,对于一个GestureDetector组件,我们可以注册onTap回调来处理点击事件。
GestureDetector(
  onTap: () {
    print('Button tapped');
  },
  child: Container(
    width: 200,
    height: 50,
    color: Colors.green,
    child: Center(
      child: Text('Tap me'),
    ),
  ),
)

当用户点击这个绿色的Container时,onTap回调函数会被执行,打印出Button tapped

引擎层的工作原理

  1. 图形渲染
    • 引擎层采用Skia图形库进行图形渲染。Skia是一个功能强大的2D图形库,它支持多种图形操作,如绘制路径、位图、文本等。
    • 当引擎层接收到框架层发送的绘制消息时,它会根据消息中的组件描述信息,使用Skia库将其绘制到屏幕上。
    • 例如,对于一个Text组件,引擎层会根据字体信息、文本内容等,使用Skia的文本绘制功能将文本绘制到指定的位置。
    • 在渲染过程中,引擎层会采用一些优化策略,如分层渲染、缓存等。分层渲染可以将不同的组件或组件部分渲染到不同的层,然后在合成阶段将这些层合并在一起,提高渲染效率。缓存则可以避免重复绘制一些不变的图形元素。
  2. 动画处理
    • 引擎层负责处理动画相关的计算和渲染。Flutter的动画系统基于Animation类,框架层会将动画的参数(如起始值、结束值、持续时间等)发送给引擎层。
    • 引擎层会根据这些参数,在每一帧计算动画的当前值,并根据当前值更新UI的状态。例如,对于一个AnimatedContainer组件,当动画进行时,引擎层会不断计算容器的新大小、颜色等属性值,并通知框架层进行UI的更新。
class AnimatedContainerPage extends StatefulWidget {
  @override
  _AnimatedContainerPageState createState() => _AnimatedContainerPageState();
}

class _AnimatedContainerPageState extends State<AnimatedContainerPage>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
    );
    _animation = Tween<double>(begin: 100, end: 200).animate(_controller);
    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Animated Container'),
      ),
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Container(
              width: _animation.value,
              height: _animation.value,
              color: Colors.yellow,
            );
          },
        ),
      ),
    );
  }
}

在这个例子中,AnimationController控制动画的播放,Tween定义了动画的起始和结束值。引擎层会根据_animation的值不断更新Container的大小,实现动画效果。

  1. 文本排版
    • 引擎层负责文本的排版工作。它会根据文本的内容、字体、字号等信息,将文本正确地排版在屏幕上。
    • 文本排版涉及到复杂的算法,如断行、对齐等。引擎层会根据不同语言的特性和排版规则进行处理。例如,对于中文文本,可能需要考虑汉字的宽度、标点符号的位置等;对于英文文本,需要考虑单词的拆分和连字符的使用等。
    • 当框架层发送包含文本组件的绘制消息时,引擎层会解析文本相关的属性,使用Skia库的文本排版功能进行精确的排版操作。

框架层与引擎层协同工作流程

  1. 应用启动阶段
    • 当Flutter应用启动时,嵌入层会进行一些平台相关的初始化工作,如创建Flutter视图等。
    • 然后,框架层开始构建应用的根组件,生成组件树的初始结构。在构建过程中,框架层会将组件的相关信息(如布局约束、样式等)以消息的形式发送给引擎层。
    • 引擎层接收到这些消息后,会根据组件信息进行初始化的绘制操作,如绘制初始的UI界面。例如,在一个简单的应用中,框架层构建MaterialApp及其子组件,将这些组件的布局和样式信息传递给引擎层,引擎层使用Skia库绘制出初始的屏幕界面,包括AppBarBody等部分。
  2. 运行时状态变化阶段
    • 在应用运行过程中,当用户与UI进行交互(如点击按钮、滑动屏幕等)或者组件的状态发生变化(如StatefulWidget的状态更新)时,框架层会捕获这些变化。
    • 框架层会根据这些变化重新构建组件树的相关部分,并将新的组件信息以消息的形式发送给引擎层。例如,当一个StatefulWidget的状态变化导致其内部的Text组件文本内容改变时,框架层会重新构建包含该Text组件的部分组件树,并将新的Text组件信息(如新的文本内容、字体样式等)发送给引擎层。
    • 引擎层接收到消息后,会根据新的组件信息更新相应的UI部分。它会使用Skia库重新绘制改变的区域,如重新绘制修改后的Text组件,实现UI的实时更新。
  3. 动画运行阶段
    • 当应用中有动画运行时,框架层会将动画的参数(如起始值、结束值、持续时间等)发送给引擎层。
    • 引擎层会在每一帧计算动画的当前值,并根据当前值更新UI的状态。例如,对于一个AnimatedContainer组件的动画,引擎层会根据动画的进展计算Container的新大小、颜色等属性值。
    • 然后,引擎层将更新后的UI状态信息反馈给框架层,框架层根据这些信息重新构建相关的组件树部分,并再次通知引擎层进行绘制,以实现流畅的动画效果。

性能优化与协同工作

  1. 减少不必要的重绘
    • 框架层和引擎层协同工作可以通过减少不必要的重绘来提高性能。框架层在状态变化时,会尽量精确地确定需要重新构建的组件树部分,只将这些部分的变化信息发送给引擎层。
    • 例如,在一个复杂的列表组件中,如果只有某一项的文本内容发生变化,框架层只会重新构建该列表项对应的组件,并将其变化信息发送给引擎层,而不是整个列表的组件信息。引擎层接收到这些精确的变化信息后,只重新绘制该列表项,避免了整个列表的重绘,提高了绘制效率。
  2. 缓存与复用
    • 引擎层会对一些图形元素和资源进行缓存和复用。例如,对于一些不变的字体、位图等资源,引擎层会将其缓存起来,避免重复加载和创建。
    • 框架层也可以通过复用一些组件来提高性能。例如,在一个可滚动的列表中,框架层可以复用已经移出屏幕的列表项组件,将其重新用于新出现的列表项,减少组件的创建和销毁开销,与引擎层的缓存机制协同工作,提升整体性能。
  3. 异步处理
    • 框架层和引擎层在一些操作上采用异步处理方式。例如,当框架层发送一个复杂的绘制任务给引擎层时,引擎层可以在后台线程中进行处理,避免阻塞主线程,保证应用的流畅性。
    • 同时,框架层在处理一些耗时操作(如网络请求)时,也会采用异步方式,当数据返回后再通知引擎层进行UI更新。这样,框架层和引擎层通过异步处理的协同工作,提高了应用的响应速度和用户体验。

总结

Flutter的框架层和引擎层通过消息传递、隔离与共享状态等机制紧密协同工作。框架层负责构建和管理组件树、处理用户交互等高层任务,而引擎层专注于图形渲染、动画处理、文本排版等底层核心任务。在应用的启动、运行时状态变化以及动画运行等各个阶段,它们相互配合,实现了高效、流畅的UI呈现和用户交互体验。通过减少不必要的重绘、缓存复用以及异步处理等性能优化策略,进一步提升了Flutter应用的性能。深入理解框架层与引擎层的协同工作原理,有助于开发者更好地开发高性能、高质量的Flutter应用。