Flutter-->Widget上屏之路

本文主要介绍Flutter中创建一个Widget到屏幕上渲染出Widget内容的路程.

拾用本文您将获得:

  • Widget是什么
  • Element是什么
  • RenderObject是什么

附加Buff:

  • Widget直接渲染成图片
  • 文本String的绘制
  • 图片ui.Image的绘制

这一切都要从runApp方法开始说起, 如果你还不知道runApp是什么, 建议从
https://docs.flutter.dev/ui 开始阅读...

runApp

runApp方法就是进入Flutter世界的入口, 方法参数也是唯一的参数就是一个Widget

dart 复制代码
void runApp(Widget app) {
  final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
  _runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}

那么这个名为appWidget是怎样到界面上的呢? 开始吧...

Element

要想将Widget渲染上屏, 首先就需要将Widget变换成Element.

所以在runApp方法的执行链上, 很容易就能跟踪到下面的代码:

dart 复制代码
void attachToBuildOwner(RootWidget widget) {
  final bool isBootstrapFrame = rootElement == null;
  _readyToProduceFrames = true;
  // 请注意这里
  _rootElement = widget.attach(buildOwner!, rootElement as RootElement?);
  if (isBootstrapFrame) {
    SchedulerBinding.instance.ensureVisualUpdate();
  }
}

关键代码widget.attach(buildOwner!, rootElement as RootElement?)

dart 复制代码
/// 代码来自[RootWidget.attach]
RootElement attach(BuildOwner owner, [ RootElement? element ]) {
  if (element == null) {
    owner.lockState(() {
      element = createElement();
      assert(element != null);
      element!.assignOwner(owner);
    });
    owner.buildScope(element!, () {
      element!.mount(/* parent */ null, /* slot */ null);
    });
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element!;
}

上述代码,就是将一个Widget变换成Element的关键代码. 请注意上面的代码, 因为这玩意在另一个类中原封不动的也出现过.

那就是RenderObjectToWidgetAdapter如下:

dart 复制代码
/// 代码来自[RenderObjectToWidgetAdapter.attachToRenderTree]
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
  if (element == null) {
    owner.lockState(() {
      element = createElement();
      assert(element != null);
      element!.assignOwner(owner);
    });
    owner.buildScope(element!, () {
      element!.mount(null, null);
    });
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element!;
}

Widget变换成Element同样也是将Widget渲染成图片的关键步骤.

到这一步WidgetsBinding.rootElement就赋值完成了, 接下来就是等待屏幕信号刷新帧,开始渲染了...

上述attachToBuildOwner方法中, 有一行代码SchedulerBinding.instance.ensureVisualUpdate()就是用来安排刷新帧的, 触发之后, 等待屏幕信号即可.

dart 复制代码
/// 代码来自[SchedulerBinding.ensureVisualUpdate]
void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame();
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}
dart 复制代码
/// 代码来自[SchedulerBinding.scheduleFrame]
void scheduleFrame() {
  if (_hasScheduledFrame || !framesEnabled) {
    return;
  }
  assert(() {
    if (debugPrintScheduleFrameStacks) {
      debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
    }
    return true;
  }());
  ensureFrameCallbacksRegistered();
  platformDispatcher.scheduleFrame();
  _hasScheduledFrame = true;
}

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

PlatformDispatcher.onBeginFrame

调用链路SchedulerBinding.ensureVisualUpdate->SchedulerBinding.scheduleFrame->SchedulerBinding._handleBeginFrame->SchedulerBinding.handleBeginFrame->SchedulerBinding._invokeFrameCallback

平时通过SchedulerBinding.scheduleFrameCallback方法安排的帧回调就是在SchedulerBinding._invokeFrameCallback方法中执行的.

PlatformDispatcher.onDrawFrame

调用链路SchedulerBinding.ensureVisualUpdate->SchedulerBinding.scheduleFrame->SchedulerBinding._handleDrawFrame->SchedulerBinding.handleDrawFrame->SchedulerBinding._invokeFrameCallback

平时通过SchedulerBinding.addPersistentFrameCallbackSchedulerBinding.addPostFrameCallback方法安排的帧回调就是在这里进行处理的.

之后Flutter通过无限循环执行PlatformDispatcher.onBeginFramePlatformDispatcher.onDrawFrame方法渲染出精美的软件界面.

读到这里, 你是否注意到和WidgetsBinding.rootElement似乎一毛钱关系都没有呢?

不慌, 它在这里...

WidgetsFlutterBinding.ensureInitialized

还记得runApp方法吗?

dart 复制代码
void runApp(Widget app) {
  final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
  _runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}

这方法头一句就是WidgetsFlutterBinding.ensureInitialized, 来看看它的神秘面纱.

dart 复制代码
/// 代码来自[WidgetsFlutterBinding.ensureInitialized]
static WidgetsBinding ensureInitialized() {
  if (WidgetsBinding._instance == null) {
    WidgetsFlutterBinding();
  }
  return WidgetsBinding.instance;
}

聪明的你, 应该看出来了, 就是创建了一个单例WidgetsFlutterBinding对象. 您可千万不要被它的表象所迷惑, 这玩意可是一个巨兽...

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding

当您创建WidgetsFlutterBinding对象后会执行父类的构造方法BindingBase

dart 复制代码
BindingBase() {
  if (!kReleaseMode) {
    FlutterTimeline.startSync('Framework initialization');
  }
  assert(() {
    _debugConstructed = true;
    return true;
  }());

  assert(_debugInitializedType == null, 'Binding is already initialized to $_debugInitializedType');
  //注意这里
  initInstances();
  assert(_debugInitializedType != null);

  assert(!_debugServiceExtensionsRegistered);
  initServiceExtensions();
  assert(_debugServiceExtensionsRegistered);

  if (!kReleaseMode) {
    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
    FlutterTimeline.finishSync();
  }
}

会触发BindingBase.initInstances方法, 此方法会依次执行:

  • GestureBinding.initInstances 主要用来执行屏幕手势回调处理
dart 复制代码
@override
void initInstances() {
  super.initInstances();
  _instance = this;
  platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
}
  • SchedulerBinding.initInstances在开发阶段用来计算帧率时间等
dart 复制代码
@override
void initInstances() {
  super.initInstances();
  _instance = this;

  if (!kReleaseMode) {
    addTimingsCallback((List<FrameTiming> timings) {
      timings.forEach(_profileFramePostEvent);
    });
  }
}
  • ServicesBinding.initInstances主要用于平台服务支持等
dart 复制代码
@override
void initInstances() {
  super.initInstances();
  _instance = this;
  _defaultBinaryMessenger = createBinaryMessenger();
  _restorationManager = createRestorationManager();
  _initKeyboard();
  initLicenses();
  SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
  SystemChannels.accessibility.setMessageHandler((dynamic message) => _handleAccessibilityMessage(message as Object));
  SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
  SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage);
  platformDispatcher.onViewFocusChange = handleViewFocusChanged;
  TextInput.ensureInitialized();
  readInitialLifecycleStateFromNativeWindow();
  initializationComplete();
}
  • PaintingBinding.initInstances没想到吧, Flutter原生就有图片缓存池
dart 复制代码
@override
void initInstances() {
  super.initInstances();
  _instance = this;
  _imageCache = createImageCache();
  shaderWarmUp?.execute();
}
  • SemanticsBinding.initInstances平台一些语义,无障碍服务等
dart 复制代码
@override
void initInstances() {
  super.initInstances();
  _instance = this;
  _accessibilityFeatures = platformDispatcher.accessibilityFeatures;
  platformDispatcher
    ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
    ..onSemanticsActionEvent = _handleSemanticsActionEvent
    ..onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
  _handleSemanticsEnabledChanged();
}
  • RendererBinding.initInstances Flutter引擎渲染调度核心
dart 复制代码
@override
void initInstances() {
  super.initInstances();
  _instance = this;
  _rootPipelineOwner = createRootPipelineOwner();
  platformDispatcher
    ..onMetricsChanged = handleMetricsChanged
    ..onTextScaleFactorChanged = handleTextScaleFactorChanged
    ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged;
  addPersistentFrameCallback(_handlePersistentFrameCallback);
  initMouseTracker();
  if (kIsWeb) {
    addPostFrameCallback(_handleWebFirstFrame, debugLabel: 'RendererBinding.webFirstFrame');
  }
  rootPipelineOwner.attach(_manifold);
}

注意, 注意, 注意, 这个addPersistentFrameCallback(_handlePersistentFrameCallback)方法就是无限循环渲染的关键. addPersistentFrameCallback方法, 在前面已经介绍过了, 是不是忘记了? 往上翻一翻~~

  • WidgetsBinding.initInstances 一些和Widget有关的操作
dart 复制代码
@override
void initInstances() {
  super.initInstances();
  _instance = this;

  assert(() {
    _debugAddStackFilters();
    return true;
  }());

  // Initialization of [_buildOwner] has to be done after
  // [super.initInstances] is called, as it requires [ServicesBinding] to
  // properly setup the [defaultBinaryMessenger] instance.
  _buildOwner = BuildOwner();
  buildOwner!.onBuildScheduled = _handleBuildScheduled;
  platformDispatcher.onLocaleChanged = handleLocaleChanged;
  SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
  SystemChannels.backGesture.setMethodCallHandler(
    _handleBackGestureInvocation,
  );
  assert(() {
    FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator);
    return true;
  }());
  platformMenuDelegate = DefaultPlatformMenuDelegate();
}

去掉杂念, 让我们关注 _handlePersistentFrameCallback方法:

dart 复制代码
/// 代码来自[RendererBinding._handlePersistentFrameCallback]
void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}

/// 代码来自[RendererBinding.drawFrame]
@protected
void drawFrame() {
  rootPipelineOwner.flushLayout();
  rootPipelineOwner.flushCompositingBits();
  rootPipelineOwner.flushPaint();
  if (sendFramesToEngine) {
    for (final RenderView renderView in renderViews) {
      renderView.compositeFrame(); // this sends the bits to the GPU
    }
    rootPipelineOwner.flushSemantics(); // this sends the semantics to the OS.
    _firstFrameSent = true;
  }
}

注意到drawFrame方法了吗? 此方法在WidgetsBinding.drawFrame被重写了:

dart 复制代码
@override
void drawFrame() {
  assert(!debugBuildingDirtyElements);
  assert(() {
    debugBuildingDirtyElements = true;
    return true;
  }());

  TimingsCallback? firstFrameCallback;
  bool debugFrameWasSentToEngine = false;
  if (_needToReportFirstFrame) {
    assert(!_firstFrameCompleter.isCompleted);

    firstFrameCallback = (List<FrameTiming> timings) {
      assert(debugFrameWasSentToEngine);
      if (!kReleaseMode) {
        // Change the current user tag back to the default tag. At this point,
        // the user tag should be set to "AppStartUp" (originally set in the
        // engine), so we need to change it back to the default tag to mark
        // the end of app start up for CPU profiles.
        developer.UserTag.defaultTag.makeCurrent();
        developer.Timeline.instantSync('Rasterized first useful frame');
        developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
      }
      SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);
      firstFrameCallback = null;
      _firstFrameCompleter.complete();
    };
    // Callback is only invoked when FlutterView.render is called. When
    // sendFramesToEngine is set to false during the frame, it will not be
    // called and we need to remove the callback (see below).
    SchedulerBinding.instance.addTimingsCallback(firstFrameCallback!);
  }

  try {
  	//注意此处
    if (rootElement != null) {
      buildOwner!.buildScope(rootElement!);
    }
    super.drawFrame();
    assert(() {
      debugFrameWasSentToEngine = sendFramesToEngine;
      return true;
    }());
    buildOwner!.finalizeTree();
  } finally {
    assert(() {
      debugBuildingDirtyElements = false;
      return true;
    }());
  }
  if (!kReleaseMode) {
    if (_needToReportFirstFrame && sendFramesToEngine) {
      developer.Timeline.instantSync('Widgets built first useful frame');
    }
  }
  _needToReportFirstFrame = false;
  if (firstFrameCallback != null && !sendFramesToEngine) {
    // This frame is deferred and not the first frame sent to the engine that
    // should be reported.
    _needToReportFirstFrame = true;
    SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);
  }
}

注意到buildOwner!.buildScope(rootElement!);了吗?

Element被丢到BuildOwner对象中, 然后在super.drawFrame方法中调用了PipelineOwner.flushPaint完成一帧渲染的所有流程.

此流程在将Widget直接渲染成图片如出一辙,

直接将Widget渲染成图片代码如下:

dart 复制代码
Future<ui.Image> buildImage(Widget widget) async {
   final repaintBoundary = RenderRepaintBoundary();
   final view = ui.PlatformDispatcher.instance.implicitView ??
       RendererBinding.instance.renderView.flutterView;

   //渲染树根
   final renderView = RenderView(
     view: view,
     child: RenderPositionedBox(
       alignment: Alignment.center,
       child: repaintBoundary,
     ),
     configuration: ViewConfiguration.fromView(view),
   );

   //管道
   final pipelineOwner = PipelineOwner()..rootNode = renderView;
   renderView.prepareInitialFrame();

   //管道对象
   final buildOwner = BuildOwner(focusManager: FocusManager());

   //根元素
   final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
       container: repaintBoundary,
       child: Directionality(
         textDirection: TextDirection.ltr,
         child: widget,
       )).attachToRenderTree(buildOwner);

   //构建树
   buildOwner
     ..buildScope(rootElement)
     ..finalizeTree();

   //渲染树
   pipelineOwner
     ..flushLayout()
     ..flushCompositingBits()
     ..flushPaint();

   final image = await repaintBoundary.toImage();
   return image;
 }

总结

runApp方法的参数app是怎么到界面上的?

  • 使用RootWidget包裹app Widget
  • 然后使用RootWidget.attach方法将Widget转变成RootElement
  • Element被丢到BuildOwner对象中
  • 然后调用PipelineOwner.flushPaint完成一帧渲染

到这里, 我们还有一个东西没有介绍RenderObject, 它要来了...

RenderObject

首先, 并不是所有的Element都有RenderObject,

Element又是由Widget变换来的, 所以并不是所有的Widget都需要RenderObject.

这你纠正一点, 在ElementrenderObject是一个get方法, 所以Element能获取到renderObject, 但不一定有值...

dart 复制代码
/// 代码来自[Element.renderObject]
RenderObject? get renderObject {
  Element? current = this;
  while (current != null) {
    if (current._lifecycleState == _ElementLifecycle.defunct) {
      break;
    } else if (current is RenderObjectElement) {
      return current.renderObject;
    } else {
      current = current.renderObjectAttachingChild;
    }
  }
  return null;
}

Flutter系统里面, 只有RenderObjectElement才有renderObject, RenderObjectWidget会默认创建RenderObjectElement, 所以...

dart 复制代码
/// 代码来自[RenderObjectElement.renderObject]
@override
RenderObject get renderObject {
  assert(_renderObject != null, '$runtimeType unmounted');
  return _renderObject!;
}
RenderObject? _renderObject; //注意这里

这里顺便说一下, 平时使用的Text小部件, 文本InlineSpan是通过渲染对象RenderObject->RenderParagraph使用TextPainter绘制出来的.

平时使用的Image小部件, 图片ui.Image是通过渲染对象RenderObject>RenderImage使用paintImage方法绘制出来的

Element会经历->Element.mount->Element.update->Element.unmount可能不全, 但最核心的就这几个生命周期的回调.

RootElement根元素的mount方法在RootWidget.attach中的BuildOwner.buildScope方法中调用, 代码在之前已经提到过, 这里再来一次, 不劳烦翻页了.

dart 复制代码
/// 代码来自[RootWidget.attach]
RootElement attach(BuildOwner owner, [ RootElement? element ]) {
  if (element == null) {
    owner.lockState(() {
      element = createElement();
      assert(element != null);
      element!.assignOwner(owner);
    });
    owner.buildScope(element!, () {
      //注意这里
      element!.mount(/* parent */ null, /* slot */ null);
    });
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element!;
}

而之后子元素Elementmount方法就会在inflateWidget方法中调用了:

dart 复制代码
Element inflateWidget(Widget newWidget, Object? newSlot) {
	...
    final Element newChild = newWidget.createElement();
	...
	//注意这里
    newChild.mount(this, newSlot);
    ...
    return newChild;
  } finally {
    if (isTimelineTracked) {
      FlutterTimeline.finishSync();
    }
  }
}

方法调用链:RootElement.mount->RootElement._rebuild->Element.updateChild->Element.inflateWidget->Widget.createElement->Element.mount

火车就这样开起来了...

RenderObjectElement._renderObject对象就是在RenderObjectElement.mount方法中调用RenderObjectWidget.createRenderObject方法赋值的.

dart 复制代码
/// 代码来自[RenderObjectElement.mount]
@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  assert(() {
    _debugDoingBuild = true;
    return true;
  }());
  //注意此处
  _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
  assert(!_renderObject!.debugDisposed!);
  assert(() {
    _debugDoingBuild = false;
    return true;
  }());
  assert(() {
    _debugUpdateRenderObjectOwner();
    return true;
  }());
  assert(slot == newSlot);
  attachRenderObject(newSlot);
  super.performRebuild(); // clears the "dirty" flag
}

然后RenderObject.paint方法会被调用, 用来绘制, 里面有熟悉的canvas对象, 这对于Android开发的同学, 再熟悉不过了把?

dart 复制代码
/// 代码来自[RenderObject.paint]
void paint(PaintingContext context, Offset offset) { 
	final canvas = context.canvas; 
}

RenderObject对象中有:

  • performLayout方法, 用来实现布局(类似Android中的onMeasureonLayout)
  • paint方法, 用来实现自绘(类似Android中的onDraw)
  • handleEvent方法, 用来处理手势事件(类似Android中的onTouchEvent)

我将在之后的文章中介绍Flutter中的自定义控件:

  • 自定义自绘Widget(类似于自定义Android中的View)
  • 自定义布局Widget(类似于自定义Android中的ViewGroup)

总结

Widget是什么?

用来变换成Element的配置对象.

Element是什么?

用来创建最终的RenderObject对象.

RenderObject是什么?

使用Canvas绘制的, 界面上能看到的都是绘制出来的. 其余类其实都是控制在什么地方绘制用的.

至此文章就结束了! 感谢读者的宝贵时间~


群内有各(pian)种(ni)各(jin)样(qun)的大佬,等你来撩.

联系作者

点此QQ对话 该死的空格 点此快速加群

相关推荐
马拉萨的春天3 小时前
flutter的widget的执行顺序,单个组建的执行顺序
flutter
怀君3 小时前
Flutter——数据库Drift开发详细教程(七)
数据库·flutter
月伤593 小时前
Flutter中将bytes转换成XFile对象上传
flutter
柿蒂5 小时前
Flutter 拖动会比原生更省资源?分析 GPU呈现模式条形图不动的秘密
android·flutter
杉木笙6 小时前
Flutter 代码雨实现(矩阵雨)DLC 多图层
前端·flutter
耳東陈9 小时前
Flutter开箱即用一站式解决方案2.0-支持任意Widget的跑马灯
flutter
耳東陈9 小时前
Flutter开箱即用一站式解决方案2.0-智能刷新列表
flutter
Zsnoin能12 小时前
《Flutter 接入 Wallhaven API 惊魂记:从技术狂喜到 “社死” 边缘的 24 小时!》
windows·flutter
张风捷特烈13 小时前
每日一题 Flutter#12 | StatefulWidget 从诞生到状态类build 的流程
android·flutter·面试
吴敬悦1 天前
在 Flutter 中集成 C/C++ 代码 BLE LC3( 基于 AI 教程 )
flutter·ai编程