前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >flutter:一个bug的源码分析

flutter:一个bug的源码分析

作者头像
韦东锏
发布2022-11-07 13:10:33
3750
发布2022-11-07 13:10:33
举报
文章被收录于专栏:Android码农

由一个bug引发的flutter的widget跟element关系的源码分析

bug现象

在页面本来有照片数据的(第一张照片数据),点击加号唤起系统拍照功能后,再返回页面A,原来的照片数据丢失了(部分Android机型上必现)

bug原因和修复

照片跟UI是一个statefulWidget,照片数据是放在widget的class下面的,在调起拍照后,返回app,系统触发了widget的build,widget被重新创建了,所以保留的List数组数就没了

代码语言:javascript
复制
class TestWidget extends StatefulWidget {
  // 这个数组,用来保存照片信息
  // widget在每次build后会重新创建,之前photoList的数据就丢失了
  List<String> photoList = <String>[];

  @override
  State<TestWidget> createState() => _TestWidgetState();

  @override
  StatefulElement createElement() {
    return TestElement(this);
  }
}

修复方法也很简单,把数组挪到state的class里面就好了,虽然widget会重新创建,但是state还是原来的state,不会重新创建

代码语言:javascript
复制
class _TestWidgetState extends State<TestWidget> {
  // 数据放在State里面,而不是widget里面
  List<String> photoList = <String>[];
}

虽然bug当时就修复了,但是为什么系统的表现是这样,还是要去查看源码

创建一个demo来分析

为了方便分析问题,创建一个自定义的statefulWidget跟StatefulElement

代码语言:javascript
复制
class _TestWidgetState extends State<TestWidget> {
 
  _TestWidgetState() {
    log('_TestWidgetState created');
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: Colors.red,
    );
  }
}

class TestElement extends StatefulElement {
  TestElement(StatefulWidget widget) : super(widget);

  @override
  Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    log('updateChild child $child  newWidget $newWidget newSlot  $newSlot ');
    return super.updateChild(child, newWidget, newSlot);
  }
}

每次执行到对应的系统方法,可以打印log,也方便调试源码;然后把这个widget添加到布局中,验证widget在布局的第一次加载和后续的更新中的element的表现

widget首次加载在页面启动,widget首次加载的log如下,先是新建了widget,然后新建了element,又新建了state

代码语言:javascript
复制
[log] TestWidget create
[log] createElement
[log] _TestWidgetState created

widget的更新

然后是页面调用setState,触发页面的刷新,log可以发现,widget被重新创建,而element跟state都没有重新创建

代码语言:javascript
复制
[log] TestWidget create

const的widget在这个demo的widget前面加上const,代表是不变的,在每次调用setState刷新的情况下,widget不会被重新创建了

代码语言:javascript
复制
    return Column(
      children: [
        const TestWidget()
      ],
    );

源码分析

接下来,从源码角度分析下上述行为

widget树的目的,是为了生成element树,核心的方法就是在updateChild这里生成,源码如下,相关的代码我直接加上了注释,删掉一些无关的代码

代码语言:javascript
复制
  Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    // 如果对应的widget是空的,则直接把element移除
    // deactivateChild会把child这个element移除
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }

    final Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
      // 后续的刷新,其实是走到这里,widget重新创建了
      // 创建后的widget跟旧的wiget是同个类型的widget,于是只是更新了element对应的widget就好,不会重新创建element
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        newChild = child;
      } else {
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
    // 第一次加载,其实是走到这里,widget被创建了,但是没有对应的element
    // inflateWidget会生成这个widget对应的element
      newChild = inflateWidget(newWidget, newSlot);
    }

    return newChild;
  }

当父element创建好,就会调用updateChild去更新它的child的element,然后就会触发上面的方法,包括每次刷新,也是widget被重新创建的,不过只有两种场景下才会重新创建element

  • element为空,则会先由widget生成对应的element
  • widget的类型变了,也会重新创建对应element

第二种,判断widget有没有变的源码如下

代码语言:javascript
复制
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

会判断widget的类型和key有没有变,大多数情况下,key都是null,所以类型没变,element就也不会变

至于state的创建,其实是跟着element一起创建的

代码语言:javascript
复制
class StatefulElement extends ComponentElement {
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {

element创建的初始化方法中,会创建对应的state

总结

  1. widget是immutable的,每次build都是重新创建新的widget
  2. 在app使用过程中,有各种数据UI更新的场景,所以widget的build是很频繁的行为,但是大多数情况下,并不会重新创建element
  3. 对于不会变的widget,可以加上const前缀,就可以build的时候,避免被重新创建,提升性能
  4. 对于statefulWidget,需要把本地变量放在state里面,而不能放在widget里
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • bug现象
  • bug原因和修复
  • 创建一个demo来分析
  • 源码分析
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档