flutter 绘制过程 系列1-Binding

1、Widget

StatelessWidget和StatefulWidget都继承自Widget。

Widget作为虚类,定义了Element createElement()方法,给继承者实现,返回Element对象。

具体到StatelessWidget,实现createElement,返回StatelessElement对象。StatelessElement继承自ComponentElement。

具体到StatefulWidget,实现createElement,返回StatefulElement对象。StatefulElement继承自ComponentElement。

ComponentElement继承自Element类。

在执行createElement方法时,都会把Widget传递给Element对象,因此Element持有一个Widget对象。

2、Element

在Element树的特定位置,Element代表一个Widget实例。

多个Element形成了一颗Element树,大多数Element有一个独一无二的child,但是那些继承自RenderObjectElement的Element,就可以有多个child.

通过调用Widget.createElement方法创建一个Element,通过调用Element的mount方法,将一个新的Element添加到一个父Element的属性为slot的位置。

Element类的mount方法,负责填充所有的子Widget,并在必要时调用attachRenderObject方法,将关联的渲染对象(render objects)附着到渲染树,此时Element被标记为“active”,然后在屏幕上显示。

3、RenderObject

渲染树中的一个对象,通过RenderObjectToWidgetAdapter将其和Element联系起来。渲染树的根是RenderView,他有一个唯一的child,是RenderBox类型。在绘制阶段,将RenderObject生成对应的Layer tree,再将其生成Scene,交给GPU绘制。

总结

三者之间的基本关系就是:Element持有Widget对象,在Element的mount阶段,通过Widget对象创建RenderObject对象,这个对象被Element持有。所以Element持有Widget和RenderObject对象。

Element管理着Widget生命周期,在生命周期不同阶段,处理RenderObject不同的渲染绘制任务。

4、启动

首先从main.dart的main方法开始运行:

void main() {
  runApp(MyApp());
}

runApp方法是在一个binding.dart文件里面:

packages\flutter\lib\src\widgets\binding.dart

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

4.1 WidgetsFlutterBinding

packages\flutter\lib\src\widgets\binding.dart

WidgetsFlutterBinding类调用静态初始化方法,执行初始化操作:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

with关键字类似于java里面的implement关键字,可以初始化及调用with后面类的方法。如果对于这个关键字比较陌生的话,可以写下面的demo代码验证一下,看看初始化顺序是怎么样的:

abstract class Test1{
  Test1(){
    print("parent class");
    init();
  }

  void init(){
    print("class init");
  }
}

mixin Demo1 on Test1{
  void init(){
    print("demo1 init");
    super.init();
    print("demo1 init done");
  }
}

mixin Demo2 on Test1,Demo1 {
  void init(){
    print("demo2 init");
    super.init();
    print("demo2 init done");
  }
}

class Test2 extends Test1 with Demo1,Demo2{
  Test2(){
    print("child class");
  }
  
  static void test(){
    Test2();
  }
}

void main() {
  Test2.test();
}

parent class

demo2 init

demo1 init

class init

demo1 init done

demo2 init done

child class

透过demo我们可以知道,调用顺序是先调用父类构造函数,然后with后面的类,从后往前调用,但是因为在每个binding类的initInstances方法中,都先调用了super.initInstances方法,所以实际上先执行前面的binding类代码。先看看BindingBase的构造函数:

packages\flutter\lib\src\foundation\binding.dart

BindingBase() {
    initInstances();
}

到这里开始执行with从前到后binding的initInstances方法。

4.2 GestureBinding

绑定手势子系统,当一个点按下事件从window传递过来之后,被其拦截,由GestureBinding决定在哪一个节点生效。

@override
void initInstances() {
    super.initInstances();
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
}

获取window窗口的onPointerDataPacket方法。

4.3 ServicesBinding

监听系统消息,并通过BinaryMessenger转发。

@override
void initInstances() {
    super.initInstances();
    _instance = this;
    _defaultBinaryMessenger = createBinaryMessenger();
    window
      ..onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
    initLicenses();
    SystemChannels.system.setMessageHandler(handleSystemMessage);
}

通过createBinaryMessenger方法创建了一个默认的消息传递者。

4.4 SchedulerBinding

调度管理。调度分为几个阶段,分别是:

  • 没有frame需要处理的时候,处于Idle。
  • transientCallbacks,暂时的回调,比如更新RenderObject状态到animate。
  • midFrameMicrotasks,中间帧微服务,比如正在处理暂时回调任务的时候。
  • persistentCallbacks,持续性回调,比如layer在创建,布局,绘制阶段。
  • postFrameCallbacks,清理,并准备下一帧。
@override
void initInstances() {
    super.initInstances();
    _instance = this;
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    readInitialLifecycleStateFromNativeWindow();
    
    if (!kReleaseMode) {
      int frameNumber = 0;
      addTimingsCallback((List<FrameTiming> timings) {
        for (FrameTiming frameTiming in timings) {
          frameNumber += 1;
          _profileFramePostEvent(frameNumber, frameTiming);
        }
      });
    }
}

4.5 PaintingBinding

绑定了绘制库。

hook了缓存清理逻辑,用于清理图片缓存。

@override
void initInstances() {
    super.initInstances();
    _instance = this;
    _imageCache = createImageCache();
    if (shaderWarmUp != null) {
      shaderWarmUp.execute();
    }
}

初始化方法中创建了图片缓存对象,设置默认图片缓存大小。

shaderWarmUp其实是一个着色器预处理。在正式运行app之前,先生成一个着色器ShaderWarmUp,绘制一个场景(Scene)到里面,并缓存起来。当app里面有复杂的场景需要着色时,可以减少动画或交互时的卡顿。

着色预热操作是在GPU线程里面同步操作的,意味着,app第一帧的渲染必须等到该操作完成之后才能继续。

4.6 SemanticsBinding

Semantics是语义的意思。这个类将语义层(semantics layer)与flutter engine联系起来。

@override
void initInstances() {
    super.initInstances();
    _instance = this;
    _accessibilityFeatures = window.accessibilityFeatures;
}

4.7 RendererBinding

packages\flutter\lib\src\rendering\binding.dart

将渲染树和Flutter engine联系起来。

@override
void initInstances() {
    super.initInstances();
    _instance = this;
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
}

看看几个关键的类。

4.7.1 PipelineOwner

packages\flutter\lib\src\rendering\object.dart

PipelineOwner管理着渲染流程。

PipelineOwner对外提供接口用于驱动渲染流程。并存储着那些请求访问渲染流程中各个阶段的状态。可以通过以下步骤来刷新渲染流程:

  • flushLayout

更新任何需要重新计算布局的渲染对象。在这个阶段渲染对象的大小和布局都被计算好了。在此阶段,渲染对象的绘制和合成状态被置成dirty。

  • flushCompositingBits

渲染对象的复合bit被设置成dirty状态之后会在这个阶段更新,并且每一个渲染对象都可以知道自己的子对象是否需要被复合。这个信息在绘制阶段执行裁剪的时候会起到作用。

  • flushPaint

访问那些需要被绘制的渲染对象。在这个阶段,这些渲染对象有机会记录绘制命令到PictureLayer,以及组建其他复合Layer。

  • flushSemantics

最后,如果启用了语义,该阶段将编译渲染对象的语义。 该语义信息由辅助技术可改善渲染树的可访问性。

4.7.2 RenderView

接下来会执行initRenderView方法,创建一个RenderView对象,他继承自RenderObject类,是整个渲染树的根。

void initRenderView() {
    renderView = RenderView(configuration: createViewConfiguration(), window: window);
    renderView.prepareInitialFrame();
}

接下来RenderView的对象还调用了prepareInitialFrame方法,如下:

packages\flutter\lib\src\rendering\view.dart

void prepareInitialFrame() {
    scheduleInitialLayout();
    scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
}
  • scheduleInitialLayout方法:

packages\flutter\lib\src\rendering\object.dart

void scheduleInitialLayout() {
    _relayoutBoundary = this;
    owner._nodesNeedingLayout.add(this);
}

这个方法属于RenderObject类,由此我们看到RenderView实际调用的是父类的方法,而这个owner就是我们在initInstances方法中初始化的PipelineOwner类的对象。

_nodesNeedingLayout是一个RenderObject对象的List,全局已经初始化了,在这里先将RenderView对象添加进去,作为根。

  • _updateMatricesAndCreateNewRootLayer方法

在调用scheduleInitialPaint方法之前先调用_updateMatricesAndCreateNewRootLayer新建一个Layer对象:

packages\flutter\lib\src\rendering\view.dart

Layer _updateMatricesAndCreateNewRootLayer() {
    _rootTransform = configuration.toMatrix();
    final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
    rootLayer.attach(this);
    return rootLayer;
}

ContainerLayer其实是一个复合的Layer,可以包含很多子Layer。另外Layer继承自AbstractNode。

  • scheduleInitialPaint方法:

packages\flutter\lib\src\rendering\object.dart

void scheduleInitialPaint(ContainerLayer rootLayer) {
    _layer = rootLayer;
    owner._nodesNeedingPaint.add(this);
}

_nodesNeedingPaint是一个RenderObject对象的List,全局已经初始化了,在这里先将一个RenderView对象添加进去,另外可以看到RenderObject持有一个Layer对象。

4.7.3 addPersistentFrameCallback方法

回到RendererBinding的initInstances方法中,addPersistentFrameCallback方法添加回调,回调方法是_handlePersistentFrameCallback。

addPersistentFrameCallback方法则是把这个回调放到List中存起来,在SchedulerBinding的_persistentCallbacks阶段集中调用:

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

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

_handlePersistentFrameCallback方法里面调用的就是drawFrame方法。

4.8 WidgetsBinding

packages\flutter\lib\src\widgets\binding.dart

将widget layer与flutter engine融合在一起。

initInstances方法:

packages\flutter\lib\src\widgets\binding.dart

void initInstances() {
    super.initInstances();
    _buildOwner = BuildOwner();
    buildOwner.onBuildScheduled = _handleBuildScheduled;
}

BuildOwner类管理着widget的框架,比如跟踪哪些widget需要重建,在全局处理应用于widget树的任务,比如开发者在调试时启动热加载,就会组织那些渲染树中不活跃的列表元素,并触发重组指令。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容