Flutter生命周期

本文主要以下代码介绍flutter中element的生命周期。

1、示例

class TestWidgetLife extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Container(child: A()),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("我是row1", style: TextStyle(fontSize: 11)),
            Text("我是row2", style: TextStyle(fontSize: 11))
          ],
        )
      ],
    );
  }
}

class A extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("我是文本");
  }
}

运行结果


运行结果.png

2、展示column树结构

树型结构.png

可以看到一个widget对应一个element,只有当这个widget需要paint,layout才会对应一个RenderObject。
它们的关系是 widget用于配置element,renderobject用于显示将特定的element配置渲染在屏幕上。


element子类

上述只列举了几个element常用的子类。还有其他的比如InheritedWidget中的InheritedElement也是ComponentElement的子类。RootRenderObjectElement是RenderObjectElement的子类。

3、挂载ComponentElement

如下图,以挂载TestWidgetLife为例,左边是挂载流程, 右边这个是StatefulElement的生命期。


挂载流程

TestWidgetLife的element是StatelessElement,只有build方法 ,elment的didUpdateWidget ,activate,deactivate没有回调给使用者。而StatefulElement通过state将生命周期回调给了使用者。

//ComponentElement
 void performRebuild() {
    Widget? built;
    try {
      built = build();
    } catch (e, stack) {
    } finally {
      super.performRebuild(); // clears the "dirty" flag
    }
    try {
      _child = updateChild(_child, built, slot);
    } catch (e, stack) {
      _child = updateChild(null, built, slot);
    }
  }

//StatefulElement
 @override
  void deactivate() {
    state.deactivate();
    super.deactivate();
  }

可以看到 performRebuild中,显示调用build方法,也就是我们ComponentElement中的build方法,然后拿着build方法调用updateChild。 此时新widgte(build) 和 oldwidget 对比,是否需要更新或者移除重新加载。

4、挂载RenderObjectElement

上图展示挂载流程,相比较RenderObjectElement,ComponentElement需要调用build方法获取child。而RenderElement没有build方法,通过child参数传递的。所以RenderObject在挂载child时,就直接调用了上图的UpdateChild方法,例如SingleChildRenderObjectElement的mount方法。

//SingleChildRenderObjectElement
 @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, (widget as    SingleChildRenderObjectWidget).child, null);
  }

//RenderObjectElement  
 @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
    attachRenderObject(newSlot);
    super.performRebuild(); // clears the "dirty" flag
  }

可以看到SingleChildRenderObjectElement 先是创建了createRenderObject,然后添加到卡槽,更新child。

5、刷新ComponentElement

class SwapChildPage extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return _SwapChildState();
  }
}

class _SwapChildState extends State<SwapChildPage> {

  void _updateTitle() {
    setState(() {
      widget.title = "文本1-1";
    });
  }

  Widget? myWidget = SwipeStateFulWidget(
      // key: GlobalKey(debugLabel: "mahao"),
      );

  @override
  Widget build(BuildContext context) {
    print("0-----build");
    return Scaffold(
      appBar: AppBar(
          backgroundColor: Colors.blue,
          centerTitle: true,
          title: Text("交换child")),
      body: Align(
        child: myWidget,
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          setState(() {});
        },
      ),
    );
  }
}

5.1、先看创建打印


创建

5.2、点击onPressed,执行setState


image.png

可以看到只执行了外层的build方法。
5.3、分析
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
 // 直接移除child.   
 if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    final Element newChild;
   //如果child为null,而newchild不为空,创建一个新的element加载newWidget。
    if (child != null) {
       //表示这个child是从外面传进来的,所以同一个引用。
      if (hasSameSuperclass && child.widget == newWidget) {  
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        //表示新的widget和oldwidget是同一个widget。key和runtype一致。
       //只需要更新一下这个widget就可以了,然后重调用build方法。
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
        if (isTimelineTracked) {
          Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
        child.update(newWidget);   //会调用build方法。
        newChild = child;
      } else {
       //如果不是同一个widget,先要移除widget,在加载新的newWidget。
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      //加载新的widget.
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }

可以看到当执行到myWidgte的时候,调用updateChild(oldmyWidgetElement, myWidget),按照上面的逻辑,hasSameSuperclass && child.widget == newWidget 类型相同,并且child也是同一个,所以不会更新。
5.4、给align增加UniqueKey()打印如下

 body: Align(
        key: UniqueKey(),
        child: myWidget,
      ),
image.png

5.4.1、UniqueKey表示每次都会创建algin都是唯一的,不同的,所以在更新Align时,每次都会先移除,然后再加载Algin, align下面的子widget也会被移除,也达到移除myWidget的目的。
5.4.2、如上图,可以看到先将原来的myWidget从树上移除,然后将再重新创建一个myWidget对象,挂载到当前树上,最后上移除的旧的myWidget销毁dispose。
5.5、由于上述myWidget是同一个对象,为了测试oldWidget和newWidget不是同一个对象,将myWidget去掉,修改如下。打印。

     body: Align(
        child: SwipeStateFulWidget(
        ),
      ),
image.png

此时会调用SwipeStateFulWidget的updateWidget方法和build方法,虽然会强制内部widget build,但是不会重新创建。
5.6、通过globalkey复用之前的widget。

  body: Align(
        key: UniqueKey(),
        child: SwipeStateFulWidget(
            key: globalKey,
        ),
      ),
image.png

Align增加UniqueKey是为了让child重建,SwipeStateFulWidget增加globalkey是为了复用刚才移除的SwipeStateFulWidget。

6、更新RenderObjectElement

//RenderObjectElement
void update(covariant RenderObjectWidget newWidget) {
    super.update(newWidget);
    _performRebuild(); // calls widget.updateRenderObject()
  }

 //singleChildRenderObject
  @override
  void update(SingleChildRenderObjectWidget newWidget) {
    super.update(newWidget);
    _child = updateChild(_child, (widget as SingleChildRenderObjectWidget).child, null);
  }

也是和mount一样,直接调用了updateChild方法更新子widget。


image.png
  body: Align(
      //  key: UniqueKey(),
        child: myWidget
      ),

当调用setState(),更新align,通过build 创建align对象和之前保存的一致,所以调用align的update。看到
align里面的child保存的是SwipeStateFulWidget,newWidget是传递进来的widget。调用调用updateChild也是比较是否需要更新或者移除重新加载。

1、initState

在initState中应该做初始化配置widget的操作。如果widget注册了监听, 需要在didUpdateWidget对old对象取消监听。在dispose中取消监听。
1.1、 当widget被挂载到树中会调用initState方法。

 void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _firstBuild();
  }
  void _firstBuild() {
    final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
    state.didChangeDependencies();
    super._firstBuild();
  }

2、didChangeDependencies

2.1、首次进入

 void _firstBuild() {
    final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
    state.didChangeDependencies();
    super._firstBuild();
  }

2.2、状态改变,rebuild的时候调用

 @override
  void performRebuild() {
    if (_didChangeDependencies) {
      state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }

2.3、由不活跃变为活跃状态

void activate() {
    final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
    _lifecycleState = _ElementLifecycle.active;
    _dependencies?.clear();
    _hadUnsatisfiedDependencies = false;
    _updateInheritance();
    attachNotificationTree();
    if (_dirty) {
      owner!.scheduleBuildFor(this);
    }
    if (hadDependencies) {
      didChangeDependencies();
    }
  }

3、didUpdateWidget

当配置发生改变的时候调用,此时必须oldwidget和newWidget的runType和key相同,会强制刷新当前widget。

void update(StatefulWidget newWidget) {
    super.update(newWidget);
    final StatefulWidget oldWidget = state._widget!;
    state._widget = widget as StatefulWidget;
    final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
    rebuild(force: true);
  }

4、reassemble

热重载的时候调用,这里应该放一些和initState里面相同的初始化逻辑。

5、build

5.1、调用build()返回widget用于更新当前element的child。
5.2、调用updateChild方法展示更新child的规则。
5.21、原child(element)存在,newWidget为null,直接移除child。
5.22、原child为null,newWidget不为null, 重新为newWidget创建一个element,并且把创建的element挂载到当前的element上。
5.23、如果newWidget和child.widget是同一个对象,比如通过构造函数传递进来的。如果卡槽不一致,更新卡槽即可。
5.24、如果key和runtimeType相同,则执行更新child。调用child的update方法。
对于RenderObjectElement,则会调用,接着继续调用子控件的updateChild方法,直到最后一个控件没有child,也没有element。

  @override
  void update(SingleChildRenderObjectWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _child = updateChild(_child, (widget as SingleChildRenderObjectWidget).child, null);
  }

对于ComponentElement则会调用下面方法,接着也是调用build方法,然后再更新child,直到最后一个控件没有child也没有element停止。

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    rebuild(force: true);
  }

mount的时候,都会为每个widget创建一个element,SingleChildRenderObjectElement和ComponentElement都会把这个element作为成员变量存起来,更新的时候和newWidget比较,是否需要更新。

void performRebuild() {
    Widget? built;
    try {
      built = build();
      debugWidgetBuilderValue(widget, built);
    } finally {
      super.performRebuild(); // clears the "dirty" flag
    }
    try {
      _child = updateChild(_child, built, slot);
    } catch (e, stack) {
    }
  }

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
 // 直接移除child.   
 if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    final Element newChild;
   //如果child为null,而newchild不为空,创建一个新的element加载newWidget。
    if (child != null) {
       //表示这个child是从外面传进来的,所以同一个引用。
      if (hasSameSuperclass && child.widget == newWidget) {  
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        //表示新的widget和oldwidget是同一个widget。key和runtype一致。
       //只需要更新一下这个widget就可以了,然后重调用build方法。
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
        if (isTimelineTracked) {
          Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
        child.update(newWidget);   //会调用build方法。
        newChild = child;
      } else {
       //如果不是同一个widget,先要移除widget,在加载新的newWidget。
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      //加载新的widget.
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }

6、deactivate

当widget从树中移除的时候调用。如果重新插入到树中,会调用activate和build方法,这是一个时机去请求一些在deactivate已经释放的资源。

void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject();
    owner!._inactiveElements.add(child); // this eventually calls
  }

可以看到,从树上移除的widget对应的element都再加入到_inactiveElements中。

7、activate

Element inflateWidget(Widget newWidget, Object? newSlot) {
    final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
    try {
      final Key? key = newWidget.key;
      if (key is GlobalKey) {
        final Element? newChild = _retakeInactiveElement(key, newWidget);
        if (newChild != null) {
          newChild._activateWithParent(this, newSlot);
          final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
          return updatedChild!;
        }
      }
      final Element newChild = newWidget.createElement();
      return newChild;
    } 
  }
 
 @override
  void activate() {
    super.activate();
    state.activate();
    markNeedsBuild();
  }

可以看到当重建的时候,如果有globalkey,并且缓存的有elements,就直接重用这个element,然后会调用activate方法。
activate还会让当前widget刷新,并且会更新依赖。

8、dispose

当这个widget永久的从这个树上移除时调用。此时mount和unMount方法返回的都是false,此时设置setState会发生错误。子类重载这个方法应该释放资源。

 void _unmountAll() {
    _locked = true;
    final List<Element> elements = _elements.toList()..sort(Element._sort);
    _elements.clear();
    try {
      elements.reversed.forEach(_unmount);
    } finally {
      assert(_elements.isEmpty);
      _locked = false;
    }
  }

void _unmount(Element element) {
    element.visitChildren((Element child) {
      _unmount(child);
    });
    element.unmount();
    assert(element._lifecycleState == _ElementLifecycle.defunct);
  }

 void unmount() {
    super.unmount();
    state.dispose();
    state._element = null;
    _state = null;
  }


_unmountAll方法是在drawFrame中调用的。可以看到unmount总是先unmount子child,然后自己再unmount。所以dispose总是内部widget先调用dispose。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容