Flutter生命周期与渲染原理

Widget生命周期

生命周期的基本概念
*   什么是生命周期
    *   说白了就是回调方法(函数)
    *   让使用者知道封装好的Widget当前处于什么状态
*   有什么作用
    *   监听Widget的事件
    *   初始化数据
        *    创建数据
        *    发送网络请求
    *    内存管理
        *    销毁数据、销毁监听者
        *    销毁Timer等等

查看无状态小部件的生命周期

// 忽略当前文件未使用key的报警
// ignore_for_file: use_key_in_widget_constructors, avoid_print

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Row;
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(),
        body: MyHomePage(title: 'Flutter Demo Page2'),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String? title;
  MyHomePage({this.title}) {
    print('构造函数被调用了!');
  }

  Widget build(BuildContext context) {
    print('build方法被调用了!');
    // 上面title不传的话,默认展示123,这就是flutter中的空安全
    return Center(child: Text(title ?? '123'));
  }
}
查看日志

热重载的时候生命周期执行了一次。

问题:重新运行发现生命周期执行了两次
查看日志

实际上只执行了一次,多出来的一次是Android Studio的问题,我们不用担心。下面通过其他工具进行验证

  • 通过Xcode运行查看日志进行验证
  • 使用终端执行$ flutter run -d 'iphone 12'进行验证
Xcode运行查看日志

查看有状态小部件的生命周期

// 忽略当前文件未使用key的报警
// ignore_for_file: use_key_in_widget_constructors, avoid_print

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Row;
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(),
        body: MyHomePage(title: 'Flutter Demo Page2'),
      ),
    );
  }
}

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

  @override
  _MyHomePageState createState() {
    // TODO: implement createState
    print('createState来了!');
    return _MyHomePageState();
  }
}

// <MyHomePage>表示是MyHomePage对象的State
class _MyHomePageState extends State<MyHomePage> {
  _MyHomePageState() {
    print('State的构造方法');
  }
  
  @override
  void initState() {
    print('State的init方法');
    super.initState();
  }

  Widget build(BuildContext context) {
    print('State的build方法被调用了!');
    // 上面title不传的话,默认展示123,这就是flutter中的空安全
    return Center(child: Text(widget.title ?? '123'));
  }

  @override
  void dispose() {
    print('State的dispose');
    super.dispose();
  }
}
重新运行查看日志

State也是有状态的,它需要更新数据,当State状态发生变化,又是怎样的呢?下面给State添加数据

// <MyHomePage>表示是MyHomePage对象的State
class _MyHomePageState extends State<MyHomePage> {
  int _count = 0;

  _MyHomePageState() {
    print('State的构造方法');
  }

  @override
  void initState() {
    print('State的init方法');
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print('State的build方法被调用了!');
    return Column(
      children: [
        ElevatedButton(
            onPressed: () {
              _count++;
              setState(() {});
            },
            child: const Icon(Icons.add)),
        Text('$_count')
      ],
    );
  }

  @override
  void dispose() {
    print('State的dispose');
    super.dispose();
  }
}
热重载查看日志
点击+号按钮查看日志

点击之后只有State的build方法被调用了,也就是每次调用setState都会重新调用build方法。进入setState查看源码,主要方法是markNeedsBuild(),把setState(() {});替换成调用markNeedsBuild(),点击+号按钮依然能够触发State的build方法被调用了!

替换setState方法查看日志

数据共享InheritedWidget

有状态小部件还有一个生命周期方法didChangeDependencies

class _MyHomePageState extends State<MyHomePage> {
  int _count = 0;

  _MyHomePageState() {
    print('State的构造方法');
  }
  @override
  void initState() {
    print('State的init方法');
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print('State的build方法被调用了!');
    return Column(
      children: [
        ElevatedButton(
            onPressed: () {
              _count++;
              setState(() {});
            },
            child: const Icon(Icons.add)),
        Text('$_count')
      ],
    );
  }

  @override
  void dispose() {
    print('State的dispose');
    super.dispose();
  }

  @override
  void didChangeDependencies() {
    print('didChangeDependencies');
    super.didChangeDependencies();
  }
}
重新运行查看日志

didChangeDependencies方法的调用在build调用之前,主要用于改变依赖关系;说到依赖关系,我们就不得不提InheritedWidget数据共享小部件

  • 新建inherited_demo.dart文件
<!-- main.dart文件 -->
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Row;
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(),
        // 使用InheritedDemo小部件
        body: const InheritedDemo(),
      ),
    );
  }
}

<!-- inherited_demo.dart文件 -->
import 'package:flutter/material.dart';

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 Column(
      children: [
        Test1(_count),
        ElevatedButton(
            onPressed: () {
              _count++;
              setState(() {});
            },
            child: const Text('我是按钮'))
      ],
    );
  }
}

class Test1 extends StatelessWidget {
  final int count;
  const Test1(this.count);
  @override
  Widget build(BuildContext context) {
    return Test2(count);
    throw UnimplementedError();
  }
}

class Test2 extends StatelessWidget {
  final int count;
  const Test2(this.count);
  @override
  Widget build(BuildContext context) {
    return Test3(count);
    throw UnimplementedError();
  }
}

class Test3 extends StatefulWidget {
  final int count;
  const Test3(this.count);
  @override
  _Test3State createState() => _Test3State();
}

class _Test3State extends State<Test3> {
  @override
  void didChangeDependencies() {
    print('didChangeDependencies来了');
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {
    return Text(widget.count.toString());
  }
}
重新运行查看日志

重新运行工程didChangeDependencies方法执行了一次,点击按钮didChangeDependencies方法并没有执行。

  • 创建数据共享类
// 使用共享类,子组件之间就能共享数据
class _InheritedDemoState extends State<InheritedDemo> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    // MyData共享类
    return MyData(
        data: _count,
        child: Column(
          children: [
            Test1(_count),
            ElevatedButton(
                onPressed: () {
                  _count++;
                  setState(() {});
                },
                child: const Text('我是按钮'))
          ],
        )
    );
  }
}

// 使用了MyData的子组件才会依赖data的Widget
class _Test3State extends State<Test3> {
  @override
  void didChangeDependencies() {
    print('didChangeDependencies来了');
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {
    print('哥么我来了!');
    // 使用了MyData的子组件才会依赖data的Widget
    return Text(MyData.of(context)!.data.toString());
  }
}

// 数据共享类
class MyData extends InheritedWidget {
  final int data; //需要在子组件中共享的数据(保存点击次数)

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

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

  //该回调决定当前data发生变化时,是否通知子组件依赖data的Widget
  @override
  bool updateShouldNotify(MyData oldWidget) {
    //如果返回true,子部件中依赖数据的Widget(build函数中有数据)的didChangeDependencies会调用!
    return oldWidget.data != data;
  }
}
点击按钮成功触发didChangeDependencies方法
小结:Widget的生命周期
*   StatelessWidget
    *   1. 构造方法
    *   2. build方法
*   StatefulWidget(包含两个对象Widget 、State)
    *   Widget构造方法
    *   Widget的CreateState
    *   State的构造方法
    *   State的initState方法
    *   didChangeDependencies方法(改变依赖关系)
        *  依赖(共享数据)的InheritedWidget发生变化之后,didChangeDependencies方法才会调用!
    *   State的build
        *    当调用setState方法,会重新调用build进行渲染!
            *    setState方法内部主要是利用_element(本质就是context对象)调用markNeedsBuild()
    *   当Widget销毁的时候,调用State的dispose方法

Widget树与Render树

Widget的渲染原理

并不是所有的Widget都会被独立渲染,只有继承了RenderObjectWidget才会创建RenderObject对象;在Flutter渲染的流程中,有三棵重要的树,Flutter引擎是针对Render树进行渲染。

Widget树Element树Render树

  • Widget Tree
    Widget Tree是整个UI界面的配置,Flutter开发者通过Widget Tree告诉Framework想要绘制的UI界面是什么样的,这棵树是我们主要打交道的对象。

  • Element Tree
    Element Tree是通过Widget Tree生成的,其主要作用是维护UI元素的树形结构,并将WidgetRenderObject关联到树上。

  • RenderObject Tree
    RenderObject Tree也是通过Widget Tree生成的,其主要作用是负责界面的绘制和布局,是属于底层系统Flutter开发者一般不需要直接操作该树。

查看Widget树

Element树

查看Element定义,发现它也是一个抽象类:

abstract class Element extends DiagnosticableTree implements BuildContext {
  
  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

  Element _parent;

  @override
  Widget get widget => _widget;
  Widget _widget;

  RenderObject get renderObject { ... }

  @mustCallSuper
  void mount(Element parent, dynamic newSlot) { ... }

  @mustCallSuper
  void activate() { ... }

  @mustCallSuper
  void deactivate() { ... }

  @mustCallSuper
  void unmount() { ... }

StatefulElementStatelessElement继承自ComponentElement, ComponentElement是继承自Element的抽象类。

abstract class ComponentElement extends Element { ... }
Element的生命周期:
  • Framework调用Widget.createElement创建一个Element实例,记为element

  • Framework 调用element.mount(parentElement,newSlot)mount方法中首先调用element所对应WidgetcreateRenderObject方法创建与element相关联的RenderObject对象,然后调用element.attachRenderObject方法将element.renderObject添加到渲染树中插槽指定的位置(这一步不是必须的,一般发生在Element树结构发生变化时才需要重新attach)。插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。

  • 当有父Widget的配置数据改变时,同时其State.build返回的Widget结构与之前不同,此时就需要重新构建对应的Element树。为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的element,element节点在更新前都会调用其对应Widget的canUpdate方法,如果返回true,则复用旧Element,旧的Element会使用新Widget配置数据更新,反之则会创建一个新的Element。Widget.canUpdate主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回true,否则就会返回false。根据这个原理,当我们需要强制更新一个Widget时,可以通过指定不同的Key来避免复用。

  • 当有祖先Element决定要移除element时(如Widget树结构发生了变化,导致element对应的Widget被移除),这时该祖先Element就会调用deactivateChild 方法来移除它,移除后element.renderObject也会被从渲染树中移除,然后Framework会调用element.deactivate方法,这时element状态变为inactive状态。

  • inactive态的element将不会再显示到屏幕。为了避免在一次动画执行过程中反复创建、移除某个特定element,inactive态的element在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成active状态,Framework就会调用其unmount方法将其彻底移除,这时element的状态为defunct,它将永远不会再被插入到树中。

  • 如果element要重新插入到Element树的其它位置,如elementelement的祖先拥有一个GlobalKey(用于全局复用元素),那么Framework会先将element从现有位置移除,然后再调用其activate方法,并将其renderObject重新attach到渲染树。

StatelessWidget的Element

通过源码分析StatelessWidgetElement

查看源码
查看源码
查看源码
查看源码
Flutter断点调试源码的方式
添加断点调试
编辑断点

StatefulWidget的Element

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

  State<StatefulWidget> get state => _state;
  State<StatefulWidget> _state;
  ...
  @override
  Widget build() => state.build(this);
  ...
}

在创建StatefulElement实例时,会调用widget.createState()赋给私有变量_state,同时把widgetelement赋给_state,从而三者产生关联关系,它的build方法就是调用state.build(this),这里的this就是StatefulElement对象自己。

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

推荐阅读更多精彩内容