Flutter笔记——runApp发生了什么(源码学习)

终于放假啦,不用一直写业务代码了,也终于有点时间可以整理整理笔记啦。
我在这里先给大家拜个早年,恭祝大家新春快乐,吉祥安康啦!

拜年.gif

Flutter系列学习笔记

main

FlutterFramework在Flutter层的项目入口是main函数,默认生成的函数如下

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

1 runApp

runApp是一个顶级函数,接受一个Widget作为rootWidget,这中间发生了什么嘞?

下文以图片、源码和文本解析的方式辅助学习。由于内容较长,需要分为几个部分学习,并且序列图、记录点和实际运行顺序有出入,下文排序主要是为了能够由浅入深的铺开runApp过程中的知识点,帮助更好的理解。

2.1 过程一

runApp过程一.png
  1. main函数,调用runApp(Widget)函数
    ///同main函数,runApp函数也是一个顶级函数
    void main() {
      runApp(MyApp());
    }
    
  2. 初始化WidgetsFlutterBinding.instance
    void runApp(Widget app) {
      WidgetsFlutterBinding.ensureInitialized()
        ..scheduleAttachRootWidget(app)
        ..scheduleWarmUpFrame();
    }
    
  3. ensureInitialized函数会创建一个WidgetsFlutterBinding的单例WidgetsBinding.instance
    class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
    
      ///确认WidgetsBinding.instance是否创建成功,flutter framework工作是是否完成
      static WidgetsBinding ensureInitialized() {
        if (WidgetsBinding.instance == null)
          WidgetsFlutterBinding();
        return WidgetsBinding.instance;
      }
    }
    
  4. WidgetsFlutterBinding通过mixIn语法继承了7个BindingBase子类,完成整个FlutterFramework层次的系统功能初始化。mixIn语法可参考我的这篇文章HelloDart-MixIn,土话记录多继承机制
    abstract class BindingBase {
      
      ///会按照WidgetsFlutterBinding集成顺序,以此调用每个WidgetsFlutterBinding子类的 
      ///initInstances和initServiceExtensions初始化函数。
      BindingBase() {
        developer.Timeline.startSync('Framework initialization');
    
        assert(!_debugInitialized);
        initInstances();
        assert(_debugInitialized);
    
        assert(!_debugServiceExtensionsRegistered);
        initServiceExtensions();
        assert(_debugServiceExtensionsRegistered);
    
        developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
    
        developer.Timeline.finishSync();
      }
    }
    
  5. initInstancesinitServiceExtensions该部分比较复杂,这里先不学习了。简单介绍一下BindingBase系列子类的作用,下列内容我也是看注释的,如有错误烦请指出
    • WidgetsFlutterBinding:将FlutterFramework绑定到FlutterEngine上面
    • GestureBinding:绑定手势系统。
    • ServicesBinding:主要作用与defaultBinaryMessenger有关,用于和native通讯相关。
    • SchedulerBinding:改类也继承了ServicesBinding,主要用于调度帧渲染相关事件。
    • PaintingBinding:和painting库绑定
    • SemanticsBinding:将语义层和FlutterEngine绑定起来。
    • RendererBinding:将渲染树与FlutterEngine绑定起来。
    • WidgetsBinding:将Widget层与FlutterEngine绑定起来。
      再次重申一下,上述内容只是看注释得来的,如果错漏,感谢指出

初始化完WidgetsFlutterBinding.instance及一堆系统Binding之后,便开始下一步操作了。

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

这里使用级联语法分别调用了scheduleAttachRootWidget(Widget)scheduleWarmUpFrame函数,具体过程见下文

2.2 过程二

runApp过程二.png
  1. scheduleAttachRootWidget(app):通过级联的方式,生成了WidgetsFlutterBinding.instance静态实例并初始化一干系统功能之后,调用WidgetsFlutterBinding.scheduleAttachRootWidget(Widget root)函数。
    void runApp(Widget app) {
      WidgetsFlutterBinding.ensureInitialized()
        ..scheduleAttachRootWidget(app)
        ..scheduleWarmUpFrame();
    }
    
  2. attachRootWidget(Widget):这里异步调用WidgetsBinding.attachRootWidget(Widget rootWidget)函数,只为了尽快显示Flutter项目的UI画面。
      void scheduleAttachRootWidget(Widget rootWidget) {
        Timer.run(() {
          attachRootWidget(rootWidget);
        });
      }
    
  3. scheduleWarmUpFrame():在上面第二点中,异步调用attachRootWidget函数,只为了尽快调用该函数,去显示一个帧画面。这个函数的详细流程按下不表,等以后再学习。
      /// Schedule a frame to run as soon as possible, rather than waiting for
      /// the engine to request a frame in response to a system "Vsync" signal.
      void scheduleWarmUpFrame() {
        ...
      }
    
  4. RenderObjectToWidgetAdapter:接着第2点,attachRootWidget函数作用就是将根Widget、根Element与根RenderObject三个跟对象绑定起来,并将唯一的BuildOwner对象引用作为根对象的持有对象,通过继承关系层层传递。看代码
    ///伪代码
    class WidgetsBinding ...{
      void attachRootWidget(Widget rootWidget) {
        //创建一个RenderObjectToWidgetAdapter对象,泛型T是RenderBox类型,
        _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
          container: renderView,
          debugShortDescription: '[root]',//renderView[注释1]是连接到物理设备输出层的渲染树对象
          child: rootWidget,  //根Widget树
        ).attachToRenderTree(buildOwner, renderViewElement);
      }    
    }
    class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
      /// Creates a bridge from a [RenderObject] to an [Element] tree.
      ///
      /// Used by [WidgetsBinding] to attach the root widget to the [RenderView].
      RenderObjectToWidgetAdapter({
        this.child,
        this.container,
        this.debugShortDescription,
      }) : super(key: GlobalObjectKey(container));
    
      /// The widget below this widget in the tree.
      final Widget child;
    
      /// The [RenderObject] that is the parent of the [Element] created by this widget.
      final RenderObjectWithChildMixin<T> container;
    
      @override
      RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
    
      @override
      RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
    
      @override
      void updateRenderObject(BuildContext context, RenderObject renderObject) { }
    
      ///
      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);
          });
          // This is most likely the first time the framework is ready to produce
          // a frame. Ensure that we are asked for one.
          SchedulerBinding.instance.ensureVisualUpdate();
        } else {
          element._newWidget = this;
          element.markNeedsBuild();
        }
        return element;
      }
    }
    
  5. attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ])这里面关于element的创建先忽略,在步骤3中学习。该部分源码在第四点中,我们可以看到RenderObjectToWidgetAdapter会创建一个RenderObjectToWidgetElement对象,作为WidgetsBinding中的_renderViewElement对象。该_renderViewElement对象也就是整个Element树中的跟对象,其持有根Widget、根RenderView与BuildOwner对象。

2.3 过程三

过程三主要分析RenderObjectToWidgetAdapter.attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ])函数,期间还有一些BaseBinding内容掺杂进来,比较复杂。

runApp过程三.png

  • BuildOnwer.lockState(fn):

      ///该函数中的_debugStateLocked 布尔值,每次调用该函数_debugStateLockLevel都会+1,
      ///callback运行完之后减1,如果在_debugStateLocked等于true期间调用setState就会抛出异常。
      void lockState(void callback()) {
        assert(callback != null);
        assert(_debugStateLockLevel >= 0);
        assert(() {
          _debugStateLockLevel += 1;
          return true;
        }());
        try {
          callback();
        } finally {
          assert(() {
            _debugStateLockLevel -= 1;
            return true;
          }());
        }
        assert(_debugStateLockLevel >= 0);
      }
    
  • RenderObjectToWidgetAdapter.createElement():会创建一个RenderObjectToWidgetElement对象。

    Element依赖关系图.png

    这里先不去细看其众多父类中的属性和操作

  • element.assignOwner(owner):将owner:BuildOnwer赋值给element,该owner:BuildOnwer是整个Element树的统一管理者。

  • element.mount(null, null):上图忽略了BuildOwner.buildScope(Element context, [ VoidCallback callback ])函数了,该函数作用在于将一个Element添加进去构建域中,并调用VoidCallback函数作为回调。
    这里要着重看下element对象一众父类中的mount函数了

      owner.buildScope(element, () {
        element.mount(null, null);
      });
    class RenderObjectToWidgetElement ...{
      @override
      void mount(Element parent, dynamic newSlot) {
        assert(parent == null);
        super.mount(parent, newSlot);
        //这里的child暂时为null,忽略rebuild函数
        _rebuild();
      }
    }
    abstract class RootRenderObjectElement extends RenderObjectElement {
    
      @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
      }
    }
    ///RenderObjectElement类中,最重要的属性,_renderObject
    ///是该根Element的RenderObject对象,该RenderObject类存储
    ///了Element的位置信息,Skia是一个2D渲染框架,基于笛卡尔坐标轴
    ///RenderObject也实现了基本的渲染功能。
    abstract class RenderObjectElement extends Element {
      /// The underlying [RenderObject] for this element.
      @override
      RenderObject get renderObject => _renderObject;
      RenderObject _renderObject;
      @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        //这个回到步骤二中的RenderObjectToWidgetAdapter类,RendererBinding类初始化之后有个PipelineOwner
        //对象,PipelineOwner对象中有个rootNode对象,就是该_renderObject了
        _renderObject = widget.createRenderObject(this);
        //这里newSlot等于null,忽略
        attachRenderObject(newSlot);
        //将dirty置为false,
        _dirty = false;
      }
    }
    class Element ...{
      @mustCallSuper
      void mount(Element parent, dynamic newSlot) {
        //一顿赋值
        _parent = parent;
        _slot = newSlot;
        _depth = _parent != null ? _parent.depth + 1 : 1;
        _active = true;
        if (parent != null) // Only assign ownership if the parent is non-null
          _owner = parent.owner;
        if (widget.key is GlobalKey) {
          final GlobalKey key = widget.key;
          key._register(this);
        }
        _updateInheritance();
        assert(() {
          _debugLifecycleState = _ElementLifecycle.active;
          return true;
        }());
      }    
    }
    

    mount(Element parent, dynamic newSlot)挂载函数在于将Element挂载到Element树上去,如果有parent属性就赋值,挂载之后Element的状态变为active。

  • SchedulerBinding.instance.ensureVisualUpdate():该函数最终调用的WidgetsFlutterBinding.handleDrawFrame()函数,该部分知识在Flutter笔记——runApp发生了什么(源码学习)文章中分析学习。

      void ensureVisualUpdate() {
        switch (schedulerPhase) {
          case SchedulerPhase.idle:
          case SchedulerPhase.postFrameCallbacks:
            scheduleFrame();
            return;
          case SchedulerPhase.transientCallbacks:
          case SchedulerPhase.midFrameMicrotasks:
          case SchedulerPhase.persistentCallbacks:
            return;
        }
      }
    
  • SchedulerBinding.scheduleFrame():接着便是调用window.scheduleFrame()函数了,这是一个native函数,纯FlutterFramework部分线索就断了。

      void scheduleFrame() {
        if (_hasScheduledFrame || !_framesEnabled)
          return;
        assert(() {
          if (debugPrintScheduleFrameStacks)
            debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
          return true;
        }());
        ensureFrameCallbacksRegistered();
        window.scheduleFrame();
        _hasScheduledFrame = true;
      }
    

3 小结

这里做个runApp的简单过程图,隐藏了许多细节

runApp简单过程图.png

  1. FlutterFramework的Dart部分,程序入口是main函数。
  2. 调用runApp(Widget)函数传入一个Widget作为根Widget。
  3. Widget只是一个配置类,不是实际的UI元素。
  4. runApp通过WidgetsFlutterBindingmixIn继承一众父类进行初始化。
  5. 其中,RendererBinding父类中的renderView对象,是实际的渲染对象。
  6. 通过RenderObjectToWidgetAdapter类,生成一个RenderObjectToWidgetElement<RenderBox>类型的Element作为根Element,并让Widget、renderView和BuildOwner和根Element产生关系。
  7. 挂载根Element,调用SchedulerBinding.instance.ensureVisualUpdate()函数,等待下一帧渲染。
  8. scheduleAttachRootWidget是一个耗时操作,异步运行。runApp会优先调用scheduleWarmUpFrame()渲染预热帧。

上述文章是笔者通过对Flutter1.12版本源码进行学习的总结,如有错漏,还烦请指出纠正,十分感谢。
另外本文只是runApp的源码进行了跟踪学习,Flutter到底是如何渲染到Native设备、还有BaseBinding系列子类作用等知识有待继续学习,而渲染调用的WidgetsFlutterBinding.handleDrawFrame()函数部分知识在Flutter笔记——runApp发生了什么(源码学习)文章中分析学习哈。

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

推荐阅读更多精彩内容