Flutter 生命周期及渲染原理

Widget 生命周期

生命周期的基本概念

我们使用一个对象的时候,有时会需要知道对象的一个状态,什么时候被创建,什么时候被销毁。我们常用的生命周期方法其实本质上就是回调函数,是 Flutter 封装好的,在 Widget 的不同状态设置对应的回调方法给外部使用。

生命周期的作用

  1. 初始化数据
  • 创建变量、常量
  • 发送网络请求
  1. 监听小部件的事件
  2. 管理内存
  • 销毁数据、销毁监听者、定时器等

Widget 常见的生命周期函数

Widget 生命周期函数我们可以分为两种类型来看,无状态与有状态类型。

无状态 Widget (StatelessWidget)

当无状态 Widget 比渲染的时候会依次调用构造函数与 build 函数。

有状态 Widget (StatefulWidget)

class MyHomePage extends StatefulWidget {
  final String? title;
  MyHomePage({this.title}) {
    print('Widget 构造函数被调用了');
  }
  @override
  _MyHomePageState createState() {
    print('createState 函数被调用了');
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  _MyHomePageState() {
    print('State 构造函数被调用了');
  }
  @override
  void initState() {
    super.initState();
    print('State 的 init 函数被调用了');
  }
  @override
  Widget build(BuildContext context) {
    print('State build 函数被调用了');
    return Center(child: Text(widget.title ?? ''),);
  }

  @override
  void dispose() {
    super.dispose();
    print('State 的 dispose 函数被调用了');
  }
}

有状态的 Widget 被渲染的时候会依次调用 StatefulWidget 的构造函数、createState 函数、State 的构造函数、initState 函数、build 函数。当热重载的时候会调用 StatefulWidget 的构造函数跟 Statebuild 函数。当调用 setState 方法的时候会调用 build 函数。

通过源码可以看到 setState 其实就是 _element 调用了 markNeedsBuild 函数,只是在此之前做了一些错误的判断,这里 _element 就是 context

数据共享部件 InheritedWidget

class MyData extends InheritedWidget {
  final int data; //需要在子组件中共享的数据

  //构造方法
  const MyData({required this.data, required Widget child}) : super(child: child);

  //定义一个便捷方法,方便子组件中的 widget 获取共享数据
  static MyData? ofContext(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType();
  }

  //该回调决定当前 data 发生变化的时候,是否通知子组件(依赖 data 的子组件)
  @override
  bool updateShouldNotify(covariant InheritedWidget oldWidget) {
    print('调用了 updateShouldNotify 函数');
    //如果返回 true,子部件中依赖共享数据的 Widget(build 函数中是否使用共享数据) 的 didChangeDependencies 方法会被调用
    return (oldWidget as MyData).data != data;
  }
}

class InheritedDemo extends StatefulWidget {
  const InheritedDemo({Key? key}) : super(key: key);

  @override
  _InheritedDemoState createState() => _InheritedDemoState();
}

class _InheritedDemoState extends State<InheritedDemo> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return MyData(data: _count, child: Column(
      children: [
        TextDemo(count: _count),
        ElevatedButton(onPressed: () {
          _count++;
          setState(() {});
        }, child: const Icon(Icons.add)),
      ],
    ));
  }
}

class TextDemo extends StatefulWidget {
  final int? count;
  TextDemo({this.count});
  @override
  _TextDemoState createState() => _TextDemoState();
}

class _TextDemoState extends State<TextDemo> {
  @override
  Widget build(BuildContext context) {
    print('调用了 build 函数');
    return Text((MyData.ofContext(context) as MyData).data.toString());
  }
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('调用了 didChangeDependencies 函数');
  }
}

在我们开发的过程中一定会遇到这种场景,子部件的数据需要依赖父部件的数据,当层级比较多的话层层传递的方式就会比较麻烦,所以 Flutter 提供一个 InheritedWidget 部件,用来解决这种场景。如上案例中,我们定义了一个负责数据共享的类 MyData 继承于 InheritedWidgetMyData 的构造方法中有两个参数,data 代表需要共享的数据,child 表示依赖于数据共享的 Widget。并且我们提供了一个 ofContext 方法,供外界获取数据时候使用。使用的话,我们在 _InheritedDemoStatebuild 方法中初始化 MyData,在需要获取共享数据的子部件中通过 MyData.ofContext(context) 来获取数据,这里需要注意的是,子组件需要通过 InheritedWidget 来获取共享数据的话,其根视图部件必须是继承于 InheritedWidget 类的 MyData。当我们执行 _count++ 的时候会调用 updateShouldNotify 方法,在这里我们可以通过返回值来判断是否调用子组件的 didChangeDependencies 方法,类似于发送通知,返回值为 true 的时候就会调用,反之就不调用,我们可以根据需求在 didChangeDependencies 方法中做一些事情。

Flutter 渲染原理

Widget 树、Element 树与 RenderObjc 树

Flutter 中有三个树结构,分别是 Widget 树、Element 树与 RenderObjc 树。Widget 树很直观的就是我们页面所有 Widget 部件的结构关系,但是 Flutter 渲染引擎并不是直接渲染 Widget 树,因为 Widget 是经常发生变化的,直接渲染 Widget 树会非常消耗性能。其实 Flutter 渲染引擎渲染的是 RenderObjc 树,RenderObjc 树中每个节点是一个又一个的 RenderObjc 对象,但并不是所有的 Widget 都会生成 RenderObjc,只有 RenderObjcWidget 的子类才会生成 RenderObjc 对象,才会被渲染引擎直接渲染。

abstract class RenderObjectWidget extends Widget {
  RenderObjectElement createElement();
  RenderObject createRenderObject(BuildContext context);
}

RenderObjectWidget 类中我们需要关注 createElementcreateRenderObject 这两个方法,这两个方法都是抽象方法,所有在子类中应该有相应的实现,这里我们先来看 createRenderObject 这个方法。

class Flex extends MultiChildRenderObjectWidget {
  @override
  RenderFlex createRenderObject(BuildContext context) {
    return RenderFlex(
      direction: direction,
      mainAxisAlignment: mainAxisAlignment,
      mainAxisSize: mainAxisSize,
      crossAxisAlignment: crossAxisAlignment,
      textDirection: getEffectiveTextDirection(context),
      verticalDirection: verticalDirection,
      textBaseline: textBaseline,
      clipBehavior: clipBehavior,
    );
  }
}

RenderObjectWidget 的子类 Flex 中我们可以看到 createRenderObject 其实就是返回一个继承于 RenderObject 的子类对象 RenderFlex,并加入到 RenderObject 树中。所以只有继承于 RenderObjectWidget 的子类且实现 createRenderObject 方法的部件才能被独立渲染。

Element 树

abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
  MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}
class MultiChildRenderObjectElement extends RenderObjectElement {
  MultiChildRenderObjectElement(MultiChildRenderObjectWidget widget)
    : assert(!debugChildrenHaveDuplicateKeys(widget, widget.children)),
      super(widget);
  }

RenderObjectWidget 类中除了 createRenderObject 方法还有 createElement 方法。通过源码可以看到 RenderObjectWidget 的子类 MultiChildRenderObjectWidget 中实现了 createElement 方法,会创建一个继承于 RenderObjectElement 的子类 MultiChildRenderObjectElement 对象。

所以继承于 RenderObjectWidget 的子类除了创建 RenderObject,还会创建 Element,三个树都有。

下面我们看一下不继承于 RenderObjectWidget 的部件。

abstract class StatelessWidget extends Widget {
  StatelessElement createElement() => StatelessElement(this);
}
abstract class StatefulWidget extends Widget {
  StatefulElement createElement() => StatefulElement(this);
}

这里可以看到继承于 Widget 的子类都会实现 createElement 方法,都会创建 Element。所以可以得出结论,所有 Widget 都会创建 Element 对象,Widget 都会有对应的 Element 对象。

通过源码注释可以看到,只要有新的 Element 被添加都会调用 mount 方法。也就是说有新的 Widget 被创建的时候都会调用一次 mount 方法,因为 WidgetElement 是一一对应的关系。

  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    _renderObject = widget.createRenderObject(this);
}

如果是普通的 Element 调用完 mount 方法之后就结束了,但是继承于 RenderObjectElement 的子类在 mount 方法中会调用 createRenderObject 方法,创建 RenderObject 对象。

StatelessWidget 的 Element

abstract class Widget extends DiagnosticableTree {
  Element createElement();
}

以上只附上了关键代码,通过源码我们可以看到 Widget 类都会实现 createElement 函数。这里我们先看下 StatelessWidget 类与 Element 的关系。

abstract class StatelessWidget extends Widget {
  StatelessElement createElement() => StatelessElement(this);
}

StatelessWidgetcreateElement 方法会创建一个 StatelessElement 对象并加入到 Elment 树中,并且返回 StatelessElement 对象,创建 StatelessElement 对象的时候 StatelessWidget 自己作为参数传给 StatelessElement 对象。

class StatelessElement extends ComponentElement {
  Widget build() => widget.build(this);
}
abstract class ComponentElement extends Element {
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    assert(_child == null);
    assert(_lifecycleState == _ElementLifecycle.active);
    _firstBuild();
    assert(_child != null);
  }
abstract class Element extends DiagnosticableTree implements BuildContext {
  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

通过源码追踪可以看到 StatelessElement 继承于 ComponentElementComponentElement 继承于 Element,在 Element 的构造方法中外部传入的 widget 会被赋值给 _widget 属性,在 ComponentElement 中会调用 mount 方法, mount 方法中的 _firstBuild 最终会执行 ComponentElementbuild 方法,并且 StatelessElementbuild 会执行 widget.build(this),也就是调用 Widgetbuild 方法,如下代码所示,并把自己传给外面。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {}
}

所以我们外部 StatelessWidget 子类中的 context 就是 Element 对象。

StatefulWidget 的 Element

abstract class StatefulWidget extends Widget {
  @override
  StatefulElement createElement() => StatefulElement(this);
}

StatefulWidget 同样会执行 createElement,但是返回的对象是 StatefulElement 类型。

class StatefulElement extends ComponentElement {
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
        state._element = this;
        state._widget = widget;
}

  @override
  Widget build() => state.build(this);
}

但是 StatefulElement 多了一步就是执行 createState 函数,并且赋值给 _state 属性,并且把外部传入的 widget 赋值给 state._widget 属性,这也是我们能在 state 中能获取到 widget 的原因。并且会把 this 指针赋值给 state._element,这里 build 方法中执行的是 state.build(this)

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

推荐阅读更多精彩内容