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对象的State
class _MyHomePageState extends State {
  _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对象的State
class _MyHomePageState extends State {
  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 {
  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文件

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(),
      ),
    );
  }
}


import 'package:flutter/material.dart';

class InheritedDemo extends StatefulWidget {
  const InheritedDemo({Key? key}) : super(key: key);
  @override
  _InheritedDemoState createState() => _InheritedDemoState();
}

class _InheritedDemoState extends State {
  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 {
  @override
  void didChangeDependencies() {
    print('didChangeDependencies来了');
    super.didChangeDependencies();
  }

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

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

  • 创建数据共享类
// 使用共享类,子组件之间就能共享数据
class _InheritedDemoState extends State {
  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 {
  @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();
  }

  //该回调决定当前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 get state => _state;
  State _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)传出去

你可能感兴趣的:(Flutter生命周期与渲染原理)