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对话 该死的空格 点此快速加群

相关推荐
老田低代码20 分钟前
Dart自从引入null check后写Flutter App总有一种难受的感觉
前端·flutter
AiFlutter10 小时前
Flutter Web首次加载时添加动画
前端·flutter
ZemanZhang2 天前
Flutter启动无法运行热重载
flutter
AiFlutter2 天前
Flutter-底部选择弹窗(showModalBottomSheet)
flutter
帅次3 天前
Android Studio:驱动高效开发的全方位智能平台
android·ide·flutter·kotlin·gradle·android studio·android jetpack
程序者王大川3 天前
【前端】Flutter vs uni-app:性能对比分析
前端·flutter·uni-app·安卓·全栈·性能分析·原生
yang2952423613 天前
使用 Vue.js 将数据对象的值放入另一个数据对象中
前端·vue.js·flutter
我码玄黄3 天前
解锁定位服务:Flutter应用中的高德地图定位
前端·flutter·dart
hudawei9963 天前
flutter widget 设置GestureDetector点击无效
flutter·gesturedetector·点击事件