服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

flutter:一个bug的源码分析

日期: 来源:玉刚说收集编辑:韦东锏

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

bug现象

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

bug原因和修复

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

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,不会重新创建

class _TestWidgetState extends State<TestWidget> {
  // 数据放在State里面,而不是widget里面
  List<String> photoList = <String>[];
}

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

创建一个demo来分析

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

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

[log] TestWidget create
[log] createElement
[log] _TestWidgetState created

widget的更新

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

[log] TestWidget create

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

    return Column(
      children: [
        const TestWidget()
      ],
    );

源码分析

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

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

  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有没有变的源码如下

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

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

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

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里

相关阅读

  • Android 技术面试如何做好准备?

  • 这是 JsonChao 的第 210 期分享超友们,早上好,中秋节快乐~今天的干货来点轻松一点的,这次的分享是《技术面试如何做好准备?》,主要分为三个部分:第一部分:面试前。第二部分:面试中。
  • 芳芳频道|李艳芳三套卷复盘(数三①)

  • 往期直达数一复盘(已完结)数二复盘(已完结)李艳芳三套卷划重点数三第一套总体作为数三的第一套,自我感觉还是比较恰当的,整体计算量不大,概率题都很常规,较好得分,高数题和线代题不乏
  • 新文速递 - 9.22/9.29

  • 纳米粒子修饰的微型机器人用于治疗急性细菌性肺炎的体内抗生素递送Biohybrid microrobots consisting of nanoparticle-modified microalgae are constructed for active dr
  • 新文速递:11.28-12.2

  • 基于二甲基铵阳离子添加剂的中间相工程用于稳定钙钛矿太阳能电池The stability of halide perovskite solar cells, determined by film morphology, is paramount to their

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • flutter:一个bug的源码分析

  • 由一个bug引发的flutter的widget跟element关系的源码分析bug现象在页面本来有照片数据的(第一张照片数据),点击加号唤起系统拍照功能后,再返回页面A,原来的照片数据丢失了(部分And
  • 美团招Android技术专家

  • 大家好,美团招人了,各种级别,欢迎大家投递简历。下面的条件不需要全部满足,能满足若干个就行,因为全部满足的人灰常少!美团App-Android开发专家岗位基本需求1. 扎实的移动端研发能
  • Android 技术面试如何做好准备?

  • 这是 JsonChao 的第 210 期分享超友们,早上好,中秋节快乐~今天的干货来点轻松一点的,这次的分享是《技术面试如何做好准备?》,主要分为三个部分:第一部分:面试前。第二部分:面试中。
  • 漫画:国内都有哪些程序员大牛?

  • 第一位,求伯君求伯君,是一位生长在浙江绍兴的小哥哥。在上世纪80年代末,求伯君毅然加入了当时还名不见经传的香港金山公司,从事一款办公软件的开发。一年后,这款办公软件正式问世
  • 重庆猴痘病例详情披露,与德国株高度同源

  • “医学界”晚讯,不错过每个医疗热点。重庆猴痘病例系29岁中国籍销售人员,详情披露9月19日,中国疾控中心周报发布重庆猴痘病例相关信息:一名29岁的中国籍销售人员于9月2日至8日访