美文网首页
Flutter入门07 -- 渲染原理与Key的使用

Flutter入门07 -- 渲染原理与Key的使用

作者: zyc_在路上 | 来源:发表于2022-01-21 16:45 被阅读0次

Flutter的渲染流程

Widget
  • 在Flutter中万物皆为Widget,构成一棵Widget树,Widget可以理解为 UI界面的状态描述文件,这些描述文件在我们进行状态改变时会不断的重新build,也就是说Widget树的状态是十分不稳定的,当其状态发生变化时,就需要重新Build,那么Flutter渲染引擎渲染Widget树是非常损耗性能的;
Element
  • Element是Widget的实例,是构成Element树的元素,Element相当于虚拟的DOM,Widge描述和配置子树的样子,而Element实际去配置在Element树中特定的位置。Element最大的意义在于以最小的开销来更新RenderObject;
RenderObject
  • 是渲染树上的对象,是渲染库的核心;
  • 主要负责布局与绘制,同时也是构成渲染树的元素;

下面给一张图,详细描述了Widget,Element与RenderObject之间的关系:

image.png
第一点:PaddingRowTextTextField的继承链路
  • 从图中看出PaddingRowTextTextField四种组件的继承关系,最终都是继承自Widget,所以说在Flutter中万物皆为Widget;
  • Padding -> singleChildRenderObjectWidget -> RenderObjectWidget -> Widget
  • Row -> Flex -> singleChildRenderObjectWidget -> RenderObjectWidget -> Widget
  • Text -> statelessWidget -> Widget
  • TextField -> statefulWidget -> Widget
  • PaddingRow属于一类,都是继承自RenderObjectWidget属于渲染对象组件
  • TextTextField属于一类,没有继承自RenderObjectWidget
  • Widget类中有Element createElement()抽象方法,其作用是为当前Widget创建一个Element对象,所有继承自Widget类的组件,都可实现这个方法,为自己创建一个Element对象,也就是说一个Widget对象必定会有一个对应的Element对象;
  • 针对Padding组件,是在其父类SingleChildRenderObjectWidget中调用createElement()方法,返回一个singleChildRenderObjectElement对象;
第二点:PaddingRowTextTextField调用createElement()返回Element对象
  • 针对Padding组件,是在其父类SingleChildRenderObjectWidget中调用createElement()方法,返回一个singleChildRenderObjectElement对象;
  • 针对Row组件,是在其父类multiChildRenderObjectWidget中调用createElement()方法,返回一个mutilChildRenderObjectElement对象;
  • 针对Text组件,是在其父类statelessWidget中调用createElement()方法,返回一个statelessElement对象;
  • 针对TextField组件,是在其父类statefulWidget中调用createElement()方法,返回一个statefulElement对象;
  • 这四种组件调用createElement()方法,创建的Element对象类型均不同,最终都是继承自Element,其继承关系如下:
  • PaddingsingleChildRenderObjectElement -> RenderObjectElement -> Element
  • RowmutilChildRenderObjectElement -> RenderObjectElement -> Element
  • TextstatelessElement -> ComponmentElement -> Element
  • TextFieldstatefulElement -> ComponmentElement -> Element
第三点:Element调用mount方法
  • 当Widget的Element创建完成时,系统会自动调用Element的mount方法;
  • 针对Padding,创建的singleChildRenderObjectElement,调用其mount方法,实现如下:
@override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, widget.child, null);
  }
  • 内部调用其父类RenderObjectElementmount方法,实现如下:
 @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对象来创建渲染对象,看到这里就明确了Widget -> Element -> RenderObject之间关系了;

  • 针对RowPadding类似,最终也是调用父类RenderObjectElementmount方法,创建对应的渲染对象;

  • 针对TextTextField,创建的Element对象statelessElementstatefulElement对象,最终都是在其父类ComponmentElement中调用mount方法,实现如下:

@override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    assert(_child == null);
    assert(_active);
    _firstBuild();
    assert(_child != null);
  }
  • 内部调用_firstBuild()方法,实现如下:
void _firstBuild() {
    rebuild();
  }
  • 内部调用rebuild()方法,实现如下:
void rebuild() {
    assert(_debugLifecycleState != _ElementLifecycle.initial);
    if (!_active || !_dirty)
      return;
    assert(() {
      if (debugOnRebuildDirtyWidget != null) {
        debugOnRebuildDirtyWidget(this, _debugBuiltOnce);
      }
      if (debugPrintRebuildDirtyWidgets) {
        if (!_debugBuiltOnce) {
          debugPrint('Building $this');
          _debugBuiltOnce = true;
        } else {
          debugPrint('Rebuilding $this');
        }
      }
      return true;
    }());
    assert(_debugLifecycleState == _ElementLifecycle.active);
    assert(owner._debugStateLocked);
    Element debugPreviousBuildTarget;
    assert(() {
      debugPreviousBuildTarget = owner._debugCurrentBuildTarget;
      owner._debugCurrentBuildTarget = this;
      return true;
    }());
    performRebuild();
    assert(() {
      assert(owner._debugCurrentBuildTarget == this);
      owner._debugCurrentBuildTarget = debugPreviousBuildTarget;
      return true;
    }());
    assert(!_dirty);
  }
  • 内部的核心方法调用为performRebuild(),此方法是一个抽象方法,选中Command+Alt+B查看实现类有ComponmentElement,实现如下:
@override
  void performRebuild() {
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.startSync('${widget.runtimeType}',  arguments: timelineArgumentsIndicatingLandmarkEvent);

    assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
    Widget built;
    try {
      assert(() {
        _debugDoingBuild = true;
        return true;
      }());
      built = build();
      assert(() {
        _debugDoingBuild = false;
        return true;
      }());
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      _debugDoingBuild = false;
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      _dirty = false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
    }
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
      _child = updateChild(null, built, slot);
    }

    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.finishSync();
  }
  • 各种断言与逻辑判断,其核心调用为built = build()build()是ComponmentElement类的一个抽象方法,其子类有statelessElementstatefulElement,其实现分别如下:
@override
  Widget build() => widget.build(this);
@override
  Widget build() => _state.build(this);
  • 可以看出无状态的statelessWidget的build方法的调用流程:Widget创建完Element -> 调用Element的mount方法 -> _firstBuild() - rebuild() -> performRebuild() -> build() -> widget.build(this)
  • 有状态的statefulWidget的build方法的调用流程:Widget创建完Element -> 调用Element的mount方法 -> _firstBuild() - rebuild() -> performRebuild() -> build() -> _state.build(this)
  • 这也就解释了有状态的statefulWidget的build方法是在State中的;
  • widget.build(this)与_state.build(this)中的参数this,就是Element,也就是说Widget build(BuildContext context),中的BuildContext本质就是Element
第四点:statefulWidget的底层探索
  • statefulWidget创建的Element为StatefulElement,其构造方法如下:
StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    assert(() {
      if (!_state._debugTypesAreRight(widget)) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
          ErrorDescription(
            'The createState function for ${widget.runtimeType} returned a state '
            'of type ${_state.runtimeType}, which is not a subtype of '
            'State<${widget.runtimeType}>, violating the contract for createState.'
          ),
        ]);
      }
      return true;
    }());
    assert(_state._element == null);
    _state._element = this;
    assert(
      _state._widget == null,
      'The createState function for $widget returned an old or invalid state '
      'instance: ${_state._widget}, which is not null, violating the contract '
      'for createState.',
    );
    _state._widget = widget;
    assert(_state._debugLifecycleState == _StateLifecycle.created);
  }
  • 可以看到statefulWidget的State是在statefulElement的构造函数中创建的;
  • _state._widget = widget,将Widget绑定给State,这也就解释了在State中可以访问到Widget对象;
第五点:Element的源码探索
  • Element类的构造方法如下:
Element(Widget widget)
    : assert(widget != null),
      _widget = widget;
  • 可以看出Element引用了widget
  • RenderObjectElement继承自Element,其内部有访问renderObject渲染对象的setter与getter方法,如下所示:
@override
  RenderObject get renderObject => _renderObject;
  RenderObject _renderObject;
  • 可以看出Element引用了renderObject
  • 如果是statefulElement,还可以引用State
总结:
  • Widget树是配置信息,状态不断变化中,不稳定;
    • 内部调用createElement方法,创建与之对应的Element;
  • Element树是Widget的实例,真正保存Widget结构数据的对象;
    • Element创建完成之后,由系统的framework调用mount方法,继承自RenderObjectWidget会调用createRenderObject方法,创建渲染对象,继承自statelessWidgetstatefulWidget不会调用createRenderObject方法,最终会调用widget的build方法与state的build方法;
    • Element对widget与RenderObject以及state都有引用;
  • RenderObject渲染树是真正的渲染对象;
    • 内部包含渲染对象的布局与绘制操作;

Widget的属性 Key的应用

  • Widget的构造方法会传入一个可选参数Key,如下:
const Widget({ this.key });
  • 可选参数Key的作用是什么,现在我们通过案例来探讨一下:
案例代码一 -- StatelessWidget
import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State<SFHomePage> {
  final List<String> names = ["1111", "2222", "3333"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: ListView(
          children: names.map((name) {
            return ListItemLess(name);
          }).toList()),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          setState(() {
            names.removeAt(0);
          });
        },
      ),
    );
  }
}

class ListItemLess extends StatelessWidget {
  final String name;
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  ListItemLess(this.name);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(name,style: TextStyle(color: Colors.white,fontSize: 25)),
      height: 80,
      color: randomColor,
    );
  }
}
  • 效果图如下所示:
image.png
  • 当每次点击右下角的按钮时,都会删除第一条数据;
  • 现象:每删除一个,剩余的ListItemLess的颜色都会发生变化;
  • 原因:删除之后会调用setState方法,会重新build,重新build出来的新的ListItemLess会重新生成一个新的随机颜色;
案例代码二 -- StatefulWidget
  • 将上面的ListItemLess改成ListItemful继承自StatefulWidget,代码如下:
import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State<SFHomePage> {
  final List<String> names = ["1111", "2222", "3333"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: ListView(
          children: names.map((name) {
            return ListItemful(name);
          }).toList()),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          setState(() {
            names.removeAt(0);
          });
        },
      ),
    );
  }
}

class ListItemful extends StatefulWidget {
  final String name;
  ListItemful(this.name);

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

class _ListItemfulState extends State<ListItemful> {
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(widget.name),
      height: 80,
      color: randomColor,
    );
  }
}
  • 现象:每删除一个,都是最后一个被删除,剩余的颜色不会变化;
  • 删除之前的Widget Tree 与 Element Tree如下所示:
Snip20211029_70.png
  • 删除第一个青色widget,整个widget树会重建,而对应的Element树不会重建,Flutter SDK会根据新建widget树上对应位置的新widget与Element树中Element保存引用的旧widget,进行比对,决定当前Element是更新还是重建,判断方法为canUpdate
static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  • 当新建的widget与Element引用的旧widget的runtimeTypekey值都相同时,那么当前的Element只需要更新数据,不需要重建,可提升性能;
  • 删除第一个青色widget之后,widget树与element树如下:
image.png
  • 然后进行新旧widget的比对,将widget与Element树中Element进行遍历比较:
    • fulWidget2与element青引用的widget进行比较,发现类型与key(没有设置key)是相同的,那么element青(原来引用fulWidget1)会保留并更新引用fulWidget2,
    • fulWidget3与element粉引用的widget进行比较,发现类型与key(没有设置key)是相同的,那么element粉(原来引用fulWidget2)保留并更新引用fulWidget3
    • element绿在widget树中没有对应的widget了,直接删除;
案例代码三 -- StatefulWidget传可选参数key
import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State<SFHomePage> {
  final List<String> names = ["1111", "2222", "3333"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: ListView(
          children: names.map((name) {
            return ListItemful(name,key: ValueKey(name));
          }).toList()),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          setState(() {
            names.removeAt(0);
          });
        },
      ),
    );
  }
}

class ListItemful extends StatefulWidget {
  final String name;

  // ListItemful(this.name);
  ListItemful(this.name,{Key key}) : super(key: key);

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

class _ListItemfulState extends State<ListItemful> {
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(widget.name),
      height: 80,
      color: randomColor,
    );
  }
}
  • 创建ListItemful传入可选参数ValueKey(name),即key为数据内容,现在再删除item,正常删除,不会出现颜色变化与删除最后一条;
  • 判断Element更新/重建的逻辑与上面的相同,只不过现在是每一个widget都绑定一个key,重新build之后widget树中的每个 单个widget与Element树中element所引用的widget进行比对,若存在相同的runtimeType,key,那么这个element就可以重用,无需销毁重建;
image.png
  • 重建后的widget树,由于key是数据内容,所以在element树中element引用的widget的key都能匹配到,
案例代码四 -- StatefulWidget传可选参数key -- UniqueKey()
  • UniqueKey是独一无二的,也就是说每调用一次都会生成一个不带重复的key;
  • 根据上面的原理,ListItemful创建若传入UniqueKey,那么重建后的widget的key肯定在element树中element引用的widget的key都不能匹配到,所以整个Element树都会重建,所以每删除一次,所有item的颜色都会发生变化;
widget可选参数Key的分类
  • Key是一个抽象类,其有一个工厂构造器,子类有:
    • LocalKey:应用于具有相同父Element的widget进行比较,是diff算法的核心所在;
    • GlobalKey:通常使用于某个widget,然后访问其widget本身与state的;
  • LocalKey有三个子类:
    • ValueKey:我们以特定的值作Widget的key,比如字符串,数字等;
    • ObjectKey:以模型对象作为Widget的key;
    • UniqueKey:可确保key为唯一的;
GlobalKey
  • 先上案例代码,如下所示:
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("基础widget")),
        body: SFHomeContent(key: homeKey),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.gesture),
        onPressed: (){
          
        },
      ),
    );
  }
}

class SFHomeContent extends StatefulWidget {
  final String name = "2222";

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

class _SFHomeContentState extends State<SFHomeContent> {
  final String message = "1111";

  @override
  Widget build(BuildContext context) {
    return Text(message);
  }

  void test(){
    print("_SFHomeContentState test");
  }
}
  • 现在要实现,在点击按钮的时候 能访问SFHomeContent的name属性,能访问_SFHomeContentState的message属性,以及调用test方法,可通过GlobalKey来实现;
  • GlobalKey类的定义是:abstract class GlobalKey<T extends State<StatefulWidget>> extends Key,易知GlobalKey是一个抽象类,从泛型可以看出其本质是一个State,
  • 通过创建一个GlobalKey<_SFHomeContentState>,然后传参给SFHomeContent,修改后的代码如下:
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {

  final GlobalKey<_SFHomeContentState> homeKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("基础widget")),
        body: SFHomeContent(key: homeKey),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.gesture),
        onPressed: (){
          print(homeKey.currentState.message);
          print(homeKey.currentState.widget.name);
          homeKey.currentState.test();
        },
      ),
    );
  }
}

class SFHomeContent extends StatefulWidget {
  final String name = "2222";

  SFHomeContent({Key key}) : super(key: key);

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

class _SFHomeContentState extends State<SFHomeContent> {
  final String message = "1111";

  @override
  Widget build(BuildContext context) {
    return Text(message);
  }

  void test(){
    print("_SFHomeContentState test");
  }
}

相关文章

网友评论

      本文标题:Flutter入门07 -- 渲染原理与Key的使用

      本文链接:https://www.haomeiwen.com/subject/uxlehrtx.html