flutter 专题四十七 Flutter 应用启动流程分析

众所周知,任何应用程序的启动都是从main()函数开始的,Flutter也不例外,main.dart文件的main函数开始的,代码如下。

复制代码
void main() => runApp(MyApp());

main函数则调用的是runApp函数,源码如下。

复制代码
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

上面代码中,用到了一个级联运算符(...),代表的含义是WidgetsFlutterBinding.ensureInitialized()生成的对象再调用scheduleAttachRootWidget和scheduleWarmUpFrame两个方法。先简单介绍下这三行代码的作用。

  1. 生成一个Flutter Engine(C++代码)和Flutter Framework(Dart代码)的中间桥接对象;
  2. 根据app生成一个渲染树;
  3. 绘制热身帧, 将渲染树生成的Layer图层通过Flutter Engine渲染到Flutter View上。

下面,我们分别对WidgetsFlutterBinding、scheduleAttachRootWidget和scheduleWarmUpFrame来分析Flutter的启动流程。

一、 WidgetsFlutterBinding

点击打开WidgetsFlutterBinding类,该类的代码如下所示。

复制代码
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      //构造方法调用
      WidgetsFlutterBinding();
    //返回对象WidgetsBinding
    return WidgetsBinding.instance!;
  }
}

可以看到,WidgetsFlutterBinding继承自BindingBase,混入了GestureBinding,SchedulerBinding,ServicesBinding,PaintingBinding,SemanticsBinding,RendererBinding和WidgetsBinding7个mixin。

ensureInitialized方法就是获取WidgetsBinding.instance单例的过程。由于mixin没有构造方法,所以WidgetsFlutterBinding()实际调用的是父类BindingBase的构造方法,代码如下。

复制代码
BindingBase() {
  // 调用initInstances
  initInstances();
}

WidgetsFlutterBinding混入的7个mixin都重写了initInstances()方法,所以他们各自的initInstances()都会被调用,调用的逻辑如下图所示。

通过使用mixin设计,实现了高内聚低耦合和模块职责单一,并且通过mixin依赖,实现了initInstances()方法调用的串行按执行顺序。

1.1 FlutterView

FlutterView是Flutter Engine给Flutter Framework开放的用户界面和事件的接口,可以把Flutter Framework理解为围绕FlutterView的一个处理框架。所以其重要性不言而喻。上面WidgetsFlutterBinding混入的多个mixin主要就是处理window对象(即FlutterView对象的)的回调事件和提交渲染内容,所以理解FlutterView是非常有必要的。事实上,window对象是BindingBase的一个变量。

复制代码
<!-- BindingBase -->
ui.SingletonFlutterWindow get window => ui.window;

ui.window是PlatformDispatcher.instance中windowId为0的主window,如下所示。

复制代码
<!-- window.dart -->
final SingletonFlutterWindow window = SingletonFlutterWindow._(0, PlatformDispatcher.instance);

SingletonFlutterWindow的继承关系如下。

复制代码
abstract class FlutterView {}
class FlutterWindow extends FlutterView {}
class SingletonFlutterWindow extends FlutterWindow {}

FlutterView是一个抽象类,完整的代码如下。

复制代码
abstract class FlutterView {

  PlatformDispatcher get platformDispatcher;
  ViewConfiguration get viewConfiguration;
  double get devicePixelRatio => viewConfiguration.devicePixelRatio;
  Rect get physicalGeometry => viewConfiguration.geometry;
  Size get physicalSize => viewConfiguration.geometry.size; 
  WindowPadding get viewInsets => viewConfiguration.viewInsets;
  WindowPadding get viewPadding => viewConfiguration.viewPadding;
  WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets;
  WindowPadding get padding => viewConfiguration.padding;
  void render(Scene scene) => _render(scene, this);
  void _render(Scene scene, FlutterView view) native 'PlatformConfiguration_render';
}

FlutterView有几个重要的属性和方法。

  1. PlatformDispatcher是FlutterView的核心,FlutterView是对它的一层封装,是真正向Flutter Engine发送消息和得到回调的类;
  2. ViewConfiguration是Platform View的一些信息的描述,其中主要包括几个信息。
  • devicePixelRatio:物理像素和虚拟像素的比值。这个和手机有关,譬如iPhone手机可能是2或者3,Android手机就有可能是个小数,譬如3.5等。
  • geometry:Flutter渲染的View在Native platform中的位置和大小。
  • viewInsets:各个边显示的内容和能显示内容的边距大小;譬如:没有键盘的时候viewInsets.bottom为0,当有键盘的时候键盘挡住了一些区域,键盘底下无法显示内容,所以viewInsets.bottom就变成了键盘的高度。
  • padding:系统UI的显示区域如状态栏,这部分区域最好不要显示内容,否则有可能被覆盖了。譬如,很多iPhone顶部的刘海区域,padding.top就是其高度。
  • viewPadding:viewInsets和padding的和。
FlutterWindow

FlutterWindow没有什么功能,只是封装了一个构造方法,接下来我们来看看SingletonFlutterWindow的一些重要代码。

onMetricsChanged

evicePixelRatio, physicalSize, padding和viewInsets等的变化会触发的回调onMetricsChanged()函数。

复制代码
VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged;
set onMetricsChanged(VoidCallback? callback) {
    platformDispatcher.onMetricsChanged = callback;
}

手机设置的地区(如中国大陆),以及设置的地区更改后收到的回调onLocaleChanged,如下所示。

复制代码
Locale get locale => platformDispatcher.locale;

VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged;
set onLocaleChanged(VoidCallback? callback) {
    platformDispatcher.onLocaleChanged = callback;
}  

文字缩放倍率变化后会回调onTextScaleFactorChanged。

复制代码
  VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged;
  set onTextScaleFactorChanged(VoidCallback? callback) {
    platformDispatcher.onTextScaleFactorChanged = callback;
  }

除了上面的这几个外,其他的调用都会回调某个函数。而FlutterView对象window本质上是对PlatformDispatcher的封装,从PlatformDispatcher获取一些界面相关信息,获取从Flutter Engine 发送来的事件,然后触发和转发相应的回调方法。

1.2 BindingBase

BindingBase是一个抽象类,源码如下。

复制代码
abstract class BindingBase {
    BindingBase() {
        // 初始化
        initInstances();
    }
    // 单例window
    ui.SingletonFlutterWindow get window => ui.window;
}

BindingBase主要有两个作用:

  • 构造函数调用initInstances方法,其实是为了依次调用7个mixin的initInstances方法。
  • 提供了一个window单例。
1.3 RendererBinding

RendererBinding的功能主要和渲染树相关,打开RendererBinding的源码,首先来看initInstances()初始化方法。

复制代码
void initInstances() {
    super.initInstances();
    _instance = this;
    // 1
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    // 2
    window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    // 3
    initRenderView();
    
    _handleSemanticsEnabledChanged();
    // 4
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    // 5
    initMouseTracker();
}

在上面的代码中,我们来依次分析下都干了啥。

  1. 生成了一个PipelineOwner对象。它的主要作用是收集需要更新的RenderObjects,然后借助RendererBinding进行UI刷新。

  2. 处理window对象的onMetricsChanged、onTextScaleFactorChanged等回调方法。

  3. initRenderView生成了一个RenderView对象renderView,然后将renderView设置为_pipelineOwner的根节点rootNode。

  4. addPersistentFrameCallback调用的是SchedulerBinding的方法,PersistentFrameCallback主要执行的是Widget的build / layout / paint等一系列操作。

    void addPersistentFrameCallback(FrameCallback callback) {
    _persistentCallbacks.add(callback);
    }

5,生成一个MouseTracker对象,处理hitTestResult或者PointerAddedEvent和PointerRemovedEvent事件。

复制代码
@visibleForTesting
  void initMouseTracker([MouseTracker tracker]) {
    _mouseTracker?.dispose();
    _mouseTracker = tracker ?? MouseTracker(pointerRouter, renderView.hitTestMouseTrackers);
  }
1.4 SemanticsBinding

Semantics主要就是描述应用程序中的UI信息,而在iOS和Android主要是用于读屏使用,帮助有视力障碍的人使用,SemanticsBinding的源码如下。

复制代码
mixin SemanticsBinding on BindingBase {
    void initInstances() {
        super.initInstances();
        _instance = this;
        _accessibilityFeatures = window.accessibilityFeatures;
    }
}
1.5 PaintingBinding

PaintingBinding是一个处理图片缓存的mixin,下面我们来看看PaintingBinding的主要代码,首先看一下initInstances()初始化方法。

复制代码
mixin PaintingBinding on BindingBase, ServicesBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _imageCache = createImageCache();
    shaderWarmUp?.execute();
}
  • _imageCache是图片缓存的类,最大能存1000张图片,最大内存是100MB;
  • shaderWarmUp?.execute()是一个异步方法,初始化了一个默认的着色器,避免需要着色器的时候再初始化出现掉帧现象。

同时,createImageCache()方法内部还调用了handleMemoryPressure(),如下所示。

复制代码
void handleMemoryPressure() {
    super.handleMemoryPressure();
    imageCache?.clear();
  }

之所以这么处理,是因为图片存储非常耗内存,所以当App内存警告时需要清除掉缓存。

1.6 ServicesBinding

ServicesBinding的主要功能是接收MethodChannel和SystemChannels传递过来的消息,下面来看看ServicesBinding的主要代码。首先,还是看看initInstances()的代码。

复制代码
void initInstances() {
    super.initInstances();
    _instance = this;
    // 1
    _defaultBinaryMessenger = createBinaryMessenger();
    // 2
    _restorationManager = createRestorationManager();
    // 3
    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
    // 4
    initLicenses();
    // 5
    SystemChannels.system.setMessageHandler(handleSystemMessage);
}

下面,分别来看一下每一行代码的作用。

1,createBinaryMessenger()创建了一个MethodChannel;

2,createRestorationManager()创建了一个RestorationManager用于恢复界面数据的功能;

3,通过第一步创建的_defaultBinaryMessenger实现和Plugin插件的通信

4,initLicenses是给一些文件加上Licenses说明;

5,发送SystemChannels传递过来消息;

1.7 SchedulerBinding

SchedulerBinding主要处理任务调度,Flutter的调度分为idle、transientCallbacks、midFrameMicrotasks、persistentCallbacks和postFrameCallbacks几个阶段。

  • idle:此阶段没有绘制帧任务处理,主要处理Task,Microtask,Timer回调,用户输入和手势,以及其他一些任务。
  • transientCallbacks:这个阶段主要处理动画状态的计算和更新。
  • midFrameMicrotasks:这个阶段处理transientCallbacks阶段触发的Microtasks。
  • persistentCallbacks:这个阶段主要处理build/layout/paint等操作。
  • postFrameCallbacks:这个阶段主要在处理下一帧之前,做一些清理工作或者准备工作。

接下来,我们看一下handleAppLifecycleStateChanged()方法。

复制代码
AppLifecycleState? get lifecycleState => _lifecycleState;
void handleAppLifecycleStateChanged(AppLifecycleState state) {
    assert(state != null);
    _lifecycleState = state;
    switch (state) {
      case AppLifecycleState.resumed:
      case AppLifecycleState.inactive:
        _setFramesEnabledState(true);
        break;
      case AppLifecycleState.paused:
      case AppLifecycleState.detached:
        _setFramesEnabledState(false);
        break;
    }
}

void _setFramesEnabledState(bool enabled) {
    if (_framesEnabled == enabled)
      return;
    _framesEnabled = enabled;
    if (enabled)
      scheduleFrame();
}

上面代码的主要作用是监听生命周期变化,生命周期的状态改变设置_framesEnabled的值,如果_framesEnabled为false停止刷新界面;如果_framesEnabled为true调用scheduleFrame向Native Platform请求刷新视图的请求。接下来,再看一下scheduleFrame()方法。

复制代码
void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled)
      return;
    // 1  
    ensureFrameCallbacksRegistered();
    // 2
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }


void ensureFrameCallbacksRegistered() {
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
}

其中,ensureFrameCallbacksRegistered()是先确保向window注册了onBeginFrame和 onDrawFrame两个重要回调函数。而window.scheduleFrame()是向Native platform发起一个刷新视图的请求;发送这个请求后,Native platform会在合适的时间调用onBegineFrame和onDrawFrame这两个函数, 这两个回调会完成刷新视图所需的操作,比如更新widgets、动画、和完成渲染等。这些都完成后再调用window.scheduleFrame(),一直循环下去,直到程序退出前台或者程序退出。

接下来看一下handleBeginFrame()方法的源码。

复制代码
void handleBeginFrame(Duration? rawTimeStamp) {
    _hasScheduledFrame = false;
    try {
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
        if (!_removedIds.contains(id))
          _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
      });
      _removedIds.clear();
    } finally {
      _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
    }
}

handleBeginFrame的功能是执行_transientCallbacks中的所有函数。向transientCallbacks中添加回调主要是Ticker.scheduleTick方法,是动画框架的一部分。

接着,再看一下handleDrawFrame()方法的代码。

复制代码
void handleDrawFrame() {

    try {
      // 1
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
      // 2
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
    } finally {
      _schedulerPhase = SchedulerPhase.idle;
      _currentFrameTimeStamp = null;
    }
}

final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];
final List<FrameCallback> _postFrameCallbacks = <FrameCallback>[];  

handleDrawFrame中执行了两种回调函数,persistentCallbacks和 postFrameCallbacks中所有的回调函数。

1.8GestureBinding

GestureBinding主要处理用户的各种手势事件,初始化方法如下。

复制代码
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
    void initInstances() {
    super.initInstances();
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
  }
}

GestureBinding用_handlePointerDataPacket来处理window的onPointerDataPacket方法,_handlePointerDataPacket的源码如下。

复制代码
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
    if (!locked)
      _flushPointerEventQueue();
}

void _flushPointerEventQueue() {
    while (_pendingPointerEvents.isNotEmpty)
      handlePointerEvent(_pendingPointerEvents.removeFirst());
}

void handlePointerEvent(PointerEvent event) {
    _handlePointerEventImmediately(event);
}

void _handlePointerEventImmediately(PointerEvent event) {
    HitTestResult? hitTestResult;
    if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
      // 1
      hitTestResult = HitTestResult();
      // 2
      hitTest(hitTestResult, event.position);
      // 3
      if (event is PointerDownEvent) {
        _hitTests[event.pointer] = hitTestResult;
      }
    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      // 4
      hitTestResult = _hitTests.remove(event.pointer);
    } else if (event.down) {
      hitTestResult = _hitTests[event.pointer];
    }
    if (hitTestResult != null ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
      // 5    
      dispatchEvent(event, hitTestResult);
    }
}

_handlePointerDataPacket通过一系列的方法调用,最后调用_handlePointerEventImmediately方法。当event是PointerDownEvent或者PointerHoverEvent时,新建一个HitTestResult对象,它有一个path属性,用来记录事件传递所经过的的节点。在这里插入代码片HitTestResult把GestureBinding也加在了path中,相关代码如下。

复制代码
void hitTest(HitTestResult result, Offset position) {
  result.add(HitTestEntry(this));
}

如果event是PointerDownEvent,将这个event加入到_hitTests中, 为了在event.down-即移动的时候也能获取到它。

复制代码
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};

当event是PointerUpEvent或者PointerCancelEvent时,将这个event从_hitTests中移除。最后,调用dispatchEvent(event, hitTestResult)方法。如果您有印象,RendererBinding中我们提到过dispatchEvent方法。

复制代码
<!-- rendererBinding.dart -->
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
    _mouseTracker!.updateWithEvent(event,
          () => hitTestResult ?? renderView.hitTestMouseTrackers(event.position));
    super.dispatchEvent(event, hitTestResult);
}    

其中,最重要的调用逻辑renderView.hitTestMouseTrackers(event.position)),会从renderview一直遍历它的child,将沿途的Widget加入到path中,涉及的代码如下。

复制代码
<!-- view.dart -->
HitTestResult hitTestMouseTrackers(Offset position) {
    final BoxHitTestResult result = BoxHitTestResult();
    hitTest(result, position: position);
    return result;
}

bool hitTest(HitTestResult result, { required Offset position }) {
    if (child != null)
      child!.hitTest(BoxHitTestResult.wrap(result), position: position);
    result.add(HitTestEntry(this));
    return true;
}

<!-- box.dart -->
bool hitTest(BoxHitTestResult result, { required Offset position }) {
    if (_size!.contains(position)) {
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
        result.add(BoxHitTestEntry(this, position));
        return true;
      }
    }
    return false;
  }

当遍历完renderView的所有widget后,将hitTestResult返回给GestureBinding的dispatchEvent方法,然后遍历path数组,逐个调用handleEvent方法处理相关的事件,这和原生的触摸事件的处理流程是一致的。

复制代码
<!-- gestureBinding.dart -->
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
    for (final HitTestEntry entry in hitTestResult.path) {
      entry.target.handleEvent(event.transformed(entry.transform), entry);
    }
}

void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent) {
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
}
1.9 WidgetsBinding

WidgetsBinding主要处理widget tree的一些逻辑,初始化方法如下。

复制代码
void initInstances() {
    super.initInstances();
    _instance = this;

    // 1
    _buildOwner = BuildOwner();
    buildOwner!.onBuildScheduled = _handleBuildScheduled;
    // 2
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
}

上面代码的主要作用是初始化了一个BuildOwner对象,执行widget tree的build任务,然后再

执行了一些window的回调。至此,第一步WidgetsFlutterBinding.ensureInitialized()所涉及的知识点就介绍完了。

二、scheduleAttachRootWidget

scheduleAttachRootWidget的主要作用就是和根视图进行绑定。首先,来看一下scheduleAttachRootWidget的源码。

复制代码
void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
      attachRootWidget(rootWidget);
    });
}

void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}

scheduleAttachRootWidget异步调用了attachRootWidget方法。attachRootWidget中初始化了一个RenderObjectToWidgetAdapter对象,构造函数传入了renderView和rootWidget。renderView就是RendererBinding的initInstances方法中初始化的那个对象,rootWidget则是MyApp(),即我们看到的界面。

从构造函数的参数名我们可以看到,renderView是容器,rootWidget是这个容器的child。也就是说renderView是所有的Widget的根。

复制代码
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  RenderObjectToWidgetAdapter({
    this.child,
    required this.container,
    this.debugShortDescription,
}) : super(key: GlobalObjectKey(container));

RenderObjectToWidgetAdapter对象调用attachToRenderTree方法,把构造的工具_buildOwner传进去。attachToRenderTree的源码如下。

复制代码
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      owner.lockState(() {
        // 1
        element = createElement();
        element!.assignOwner(owner);
      });
      owner.buildScope(element!, () {
        // 2
        element!.mount(null, null);
      });
      // 3
      SchedulerBinding.instance!.ensureVisualUpdate();
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
}

创建了一个RenderObjectElement的子类RenderObjectToWidgetElement,并将构造工具buildOwner引用给了它。然后,element调用mount方法。下面,我们来看一下RenderObjectToWidgetElement的mount方法。

复制代码
// RenderObjectToWidgetElement
void mount(Element? parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _rebuild();
}

// RenderObjectElement
void mount(Element? parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    _dirty = false;
}

// Element 
void mount(Element? parent, dynamic newSlot) {
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
    if (parent != null)
      _owner = parent.owner;
    final Key? key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
  }

RenderObjectToWidgetElement的mount方法先调用Element的mount方法。主要的作用就是设置_parent,_slot,_owner,_depth等的值;

然后,调用RenderObjectElement的mount方法。创建了一个renderObject,其实就是renderView。然后把这个renderObject挂载到RenderObject Tree上,之前的RenderObject Tree没有内容,所以renderView就是根节点。

接下来,再看一下RenderObjectToWidgetElement的_rebuild方法。

复制代码
void _rebuild() {
    try {
      _child = updateChild(_child, widget.child, _rootChildSlot);
    } catch (exception, stack) {
    }
}

_rebuild的功能就是Build子Widget,代码如下。

复制代码
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
    final Element newChild;
    if (child != null) {
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        newChild = child;
      } else {
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      // 创建Element
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }

updateChild中如果child为null,newWidget不为null, 则会调用newChild = inflateWidget(newWidget, newSlot);

复制代码
Element inflateWidget(Widget newWidget, dynamic newSlot) {
    final Key? key = newWidget.key;
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    return newChild;
  }

inflateWidget先创建一个Element,然后这个Element调用mount方法。事实上,inflateWidget的主要作用就是使用buildOwner对 Widget 树---renderview->MyApp->MaterialApp... 一直Build下去,直到遍历完成。

三、scheduleWarmUpFrame

scheduleWarmUpFrame是SchedulerBinding的一个方法,如下所示。

复制代码
void scheduleWarmUpFrame() {
    Timer.run(() {
      handleBeginFrame(null);
    });
    Timer.run(() {
      handleDrawFrame();
      if (hadScheduledFrame)
        scheduleFrame();
    });

    lockEvents(() async {
      await endOfFrame;
    });
}

scheduleWarmUpFrame就是调用handleBeginFrame和handleDrawFrame方法绘制一帧呈递给GPU去显示。这里需要说明的是,scheduleWarmUpFrame是立即去绘制的,没有等待Vsyn的通知,因为启动的显示要越快越好。后面的lockEvents也是为了等待预约帧绘制完成后再去执行其他的任务。

相关推荐
程序员Ctrl喵17 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难19 小时前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡20 小时前
flutter列表中实现置顶动画
flutter
始持20 小时前
第十二讲 风格与主题统一
前端·flutter
始持20 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持20 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜21 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴21 小时前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区1 天前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎1 天前
树形选择器组件封装
前端·flutter