Flutter中UI组件的构建原理(四)

前言

在flutter中通过Widget构建想要的UI,但它并非真正的UI组件,真正的UI组件是Element,那Widget和Element是如何关联起来的呢?内部的构建过程和机制又是怎样的呢?本文将带着这里两个问题从源码角度进行分析。先从一段代码看起:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

class StudyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home:HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  State createState() => _HomePageState();
}

class _HomePageState extends State {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar:AppBar(title:Text("学习")),
      body:Column(crossAxisAlignment:CrossAxisAlignment.center,children: [
        Text("这里是一段文字"),
        RaisedButton()
      ]),
    );
  }
}

这段代码运行后默认将在屏幕上显示一个导航栏,一个文本和一个按钮。上面中HomePage继承于StatefulWidget,StatefulWidget又继承于Widget,事实上flutter中就是通过Widget的各种子类来构建我们想要的UI。那Widget就是构建UI的组件吗?答案并不是。

关于Widget的描述

/// Describes the configuration for an [Element].
///
/// Widgets are the central class hierarchy in the Flutter framework. A widget
/// is an immutable description of part of a user interface. Widgets can be
/// inflated into elements, which manage the underlying render tree.
///

可以看到Widget是一个轻量级的类,真正的UI组件是Element。Widget作为Element的配置属性被单独抽离成了一个类,这就是flutter中UI组件的一个特点

Widget首次渲染流程

1、runApp()源码

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

该函数的作用为将app作为根widget加载到屏幕上。这里采用链式调用的方式来完成,点开WidgetsFlutterBinding.ensureInitialized()可以看到,他是一个单例。接下来进入scheduleAttachRootWidget()函数

2、scheduleAttachRootWidget()源码

void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
      attachRootWidget(rootWidget);
    });
}

scheduleAttachRootWidget并没有直接调用attachRootWidget(),而是通过Timer将其作为一个event task添加到主Root isolate事件循环中,接下来进入attachRootWidget()函数

3、attachRootWidget()源码

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

这里有两个变量,renderView和buildOwner,它们分别代表什么呢?renderView代表了渲染树上的根Element对象,它是在RenderBinding类的initInstances()中初始化,buildOwner代表了整个渲染管线对象,用于驱动整个渲染,它是在RenderBinding对象中生成的

4、attachToRenderTree()源码

RenderObjectToWidgetElement attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement 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;
  }

这里分两步,第一步调用createElement()创建根Element,即RenderObjectToWidgetElement createElement() => RenderObjectToWidgetElement(this);这个方法返回的是RenderObjectToWidgetElement,它是所有Element的根Element,由对应的Widget为RenderObjectToWidgetAdapter。
第二步调用这个根Element的mount函数进行挂载。然后看一下RenderObjectToWidgetElement类的mount函数实现

@override
  void mount(Element parent, dynamic newSlot) {
    assert(parent == null);
    super.mount(parent, newSlot);
    _rebuild();
}

接着继续看父类RootRenderObjectElement的mount()函数,撒也没做。然后再进入父类RenderObjectElement的mount()函数

@override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    _renderObject = widget.createRenderObject(this);
    assert(() {
      _debugDoingBuild = false;
      return true;
    }());
    assert(() {
      _debugUpdateRenderObjectOwner();
      return true;
    }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    _dirty = false;
  }

上面_renderObject = widget.createRenderObject(this);就创建了渲染对象,这个渲染对象就作为渲染树的根对象。

Element各个子类的作用:

  • ComponentElement
    组件Element,衍生出StatelessElement和StatefulElement两个具体的组件Element,专门用于将各个基础的Widget(例如Text,RaiseButton等)通过build函数组合到一起。

  • RenderObjectElement
    渲染Element,拥有具体的RenderObject,通过createRenderObject()函数指定

  • RootRenderObjectElement
    作为渲染树中所有渲染对象的根渲染对象

接下来进入RenderObjectToWidgetElement的_rebuild()函数

void _rebuild() {
    try {
      _child = updateChild(_child, widget.child, _rootChildSlot);
      assert(_child != null);
    } catch (exception, stack) {
      final FlutterErrorDetails details = FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'widgets library',
        context: ErrorDescription('attaching to the render tree'),
      );
      FlutterError.reportError(details);
      final Widget error = ErrorWidget.builder(details);
      _child = updateChild(null, error, _rootChildSlot);
    }
}

_child = updateChild(_child, widget.child, _rootChildSlot);通过updateChild()函数去解析Widget树中层次结构中的对象,进入Element的updateChild()函数

Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {  
      if (child != null)
        deactivateChild(child);
      return null;
    }
    Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) { //关键代码1
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        assert(child.widget == newWidget);
        assert(() {
          child.owner._debugElementWasRebuilt(child);
          return true;
        }());
        newChild = child;
      } else {  //关键代码2
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {  //关键代码3
      newChild = inflateWidget(newWidget, newSlot);
    }

    assert(() {
      if (child != null)
        _debugRemoveGlobalKeyReservation(child);
      final Key key = newWidget?.key;
      if (key is GlobalKey) {
        key._debugReserveFor(this, newChild);
      }
      return true;
    }());

    return newChild;
  }

这里解析关键代码,参数child就代表当前Widget对应的Element(对于首次加载app,他指的是前面RenderObjectToWidgetElement对象实例),参数newWidget代表当前Widget的第一个子Widget(即build函数返回的那个Widget),现在看下关键代码1
通过Widget.canUpdate(child.widget, newWidget)比对当前widget和新配置对象Widget是否一样,一样则直接调用Element的update()函数,不一样则进入关键代码2
先调用deactivateChild(child);将当前Element标记为非激活Element,然后调用newChild = inflateWidget(newWidget, newSlot);重新构建当前对象的Element

接着进入inflateWidget()函数

Element inflateWidget(Widget newWidget, dynamic newSlot) {
    assert(newWidget != null);
    final Key key = newWidget.key;
    if (key is GlobalKey) {
      final Element newChild = _retakeInactiveElement(key, newWidget);
      if (newChild != null) {
        assert(newChild._parent == null);
        assert(() {
          _debugCheckForCycles(newChild);
          return true;
        }());
        newChild._activateWithParent(this, newSlot);
        final Element updatedChild = updateChild(newChild, newWidget, newSlot);
        assert(newChild == updatedChild);
        return updatedChild;
      }
    }
    final Element newChild = newWidget.createElement();
    assert(() {
      _debugCheckForCycles(newChild);
      return true;
    }());
    newChild.mount(this, newSlot);
    assert(newChild._debugLifecycleState == _ElementLifecycle.active);
    return newChild;
}

先调用final Element newChild = newWidget.createElement();创建对应的Element,然后调用它的mount()函数进行挂载,可以看到又进入到了和前面一样的流程中了
flutter就是这样通过嵌套调用的方式将Widgets树构建成Element树,那flutter是如何构建渲染树的呢?前面我们知道,Element的子类有两种CommponentElment和RenderObjectElment,前者一般用于我们构建Widgets树,它不直接拥有渲染树中的渲染对象,后者则直接拥有渲染树中的渲染对象。

当通过inflateWidget()函数不停的嵌套构建Element时,如果构建的Element为CommponentElment那么持续createElement()-->mount()-->rebuild()-->performRebuild()->updateChild()->inflateWidget()....
如果构建的Element为RenderObjectElment那么持续createElement()-->mount()->createRenderObject()->attachRenderObject() 将Element对应的渲染对象加入渲染树中

以上就是Widget树-->Element树-->渲染树的构建流程以及原理,对于我们来说,大部分情况下都是通过Widget来构建我们自己的UI界面,渲染树相关对象以及Element树中的相关对象的关联过程flutter已经帮我们封装好了。

关于RenderObjectWidget

我们都知道StatelessWidget用于构建无状态更新的Widget,StatefulWidget用于构建有状态更新的Widget,他们对应的Element是ComponentElement的子类。RenderObjectWidget则代表着具体的渲染对象的Widget,它对应的Element为RenderObjectElement,它有两个具体的子类:

  • MultiChildRenderObjectElement 代表着组合组件Element
  • SingleChildRenderObjectElement 代表着单个的Element
    只有RenderObjectElement才拥有一个RenderObject对象用于构建渲染树中的渲染对象

你可能感兴趣的:(Flutter中UI组件的构建原理(四))