flutter-状态管理1

作者: 浮华_du | 来源:发表于2021-03-27 10:50 被阅读0次

    一 . 什么是状态管理? 为什么需要状态管理?

    原生开发的同学可能对"状态"没有什么概念,因为原生开发大多使用命令式框架.比如:new 一个控件,通过set方法改变它的值;
    前端同学用过 React/Vue 的,可能对声明式编程和状态管理会熟悉些.

    flutter使用 widgets 描述 UI,当用户界面发生变化时,flutter 不会修改旧的实例,而是构造新的 widget 实例,当然为了性能只会构造需要构造实例,这一切都是由框架来计算完成的。

    根据Widget描述可知,widget是不可变的,它是对Element的一个描述;如果要实现Widget根据数据变化,需要借助State,使用StatefulWidget完成状态的变化(setState)


    image.png
    image.png

    状态管理 --就是管理数据变化和widget的更新.
    为什么需要状态管理? --当我们的项目功能交互很复杂时,一个StatefulWidget里面可能会存在很多子Widget需要刷新,就会调用很多次setState来更新控件,这势必对于性能以及代码的可阅读性带来一定的影响.

    二 . 状态管理方式

    1.StatefulWidget:最重要的方式 setState,支持规模较小的程序.使用setState会使整个Widget重新构建,如果页面很复杂,就会导致严重的性能损耗.Widget=>Element=>RenderObject
    2.inheritedWidget:它提供了一种数据在widget树中从上到下传递、共享的方式,可以实现跨组件传递共享数据.(无法跨页面)

    image.png

    3.scoped_model/redux/bloc/provide/provider/Get...框架

    三 .setState为什么可以刷新页面

    image.png

    由方法实现可以看出,setState仅仅做了两件事

      1. 调用VoidCallback fn
      1. _element!.markNeedsBuild();

    下面我们来看一下_element!.markNeedsBuild();里面到底做了什么


    image.png
    image.png
      1. 将自己标记为dirty
      1. owner.scheduleBuildFor(this);将自己加入_dirtyElements集合中

    总结:
    setState()过程其实只是将当前对应的Element标记为脏(demo中对应HomePageState),并且添加到_dirtyElements合中

    上述流程好像并没有看出,setState是如何刷新页面的,想要继续探索,我们首先需要了解一下Flutter渲染机制.

    在计算机系统中,图像的显示需要 CPU、GPU 和显示器一起配合完成:CPU 负责图像数据计算,GPU 负责图像数据渲染,而显示器则负责最终图像显示。
    CPU 把计算好的、需要显示的内容交给 GPU,由 GPU 完成渲染后放入帧缓冲区,随后视频控制器根据垂直同步信号(VSync)以每秒 60 次的速度,从帧缓冲区读取帧数据交由显示器完成图像显示。
    操作系统在呈现图像时遵循了这种机制,而 Flutter 作为跨平台开发框架也采用了这种底层方案。下面有一张更为详尽的示意图来解释 Flutter 的绘制原理。


    image.png

    开始FrameWork层会通知Engine表示自己可以进行渲染了,在下一个Vsync信号到来之时,Engine层会通过Windows.onDrawFrame回调Framework进行整个页面的构建与绘制。其实Windows.onDrawFrame 是绑定到了SchedulerBinding 的 _handleDrawFrame()
    我们重点来看下SchedulerBinding 的 _handleDrawFrame


    image.png
    image.png
    • SchedulerBinding.handleDrawFrame()中对_persistentCallbacks和_postFrameCallbacks集合进行了回调。根据上面的描述可知,_persistentCallbacks中是一些固定流程的回调,例如build,layout,paint。跟踪- - 这个_persistentCallbacks这个集合,发现在RendererBinding.initInstances()初始化中调用了addPersistentFrameCallback(_handlePersistentFrameCallback)方法。


      image.png
    • _handlePersistentFrameCallback方法


      image.png
    • drawFrame()


      image.png

      这里调用了布局,绘制,渲染帧的。而且看类名,这是负责渲染的Binding,并没有调用Widget的构建。这是因为WidgetsBinding是on RendererBinding的,其中重写了drawFrame(),实际上调用的应该是WidgetsBinding.drawFrame()

    • WidgetsBinding#drawFrame()
    @override
    void drawFrame() {
    try {
        if (renderViewElement != null)
          // buildOwner就是前面提到的负责管理widgetbuild的对象
          // 这里的renderViewElement是整个UI树的根节点
          buildOwner.buildScope(renderViewElement);
        super.drawFrame();
            //将不再活跃的节点从UI树中移除
        buildOwner.finalizeTree();
      } finally {
            /·················/
      }
    }
    

    在super.drawFrame()之前,先调用 buildOwner.buildScope(renderViewElement)。


    image.png

    刚才我们说过,setState()过程其实只是将当前对应的Element标记为脏(demo中对应HomePageState),并且添加到_dirtyElements集合中;
    而这个buildOwner.buildScope方法会对集合内的每一个对象调用rebuild()。rebuild()这个方法最终走到performRebuild(),这是一个Element中的一个抽象方法。

    这个流程,我们也可以通过断点查看


    image.png

    四 . 为什么setState ()会消耗性能

    performRebuild()

    image.png

    这个方法直接调用子类的build方法返回了一个Widget(built),对应调用前面的页面中的build方法。
    将这个新build()出来的widget和之前挂载在Element树上的_child(Element类型)作为参数,传入updateChild(_child, built, slot)中.

    updateChild(_child, built, slot)

    这个方法的上有这样的注释

    newWidget == null newWidget != null
    child == null Returns null. Returns new [Element].
    child != null Old child is removed, returns null. Old child updated if possible, returns child or new [Element].

    如果之前的位置child为null
    A、如果newWidget为null的话,说明这个位置始终没有子节点,直接返回null即可。
    B、如果newWidget不为null,说明这个位置 新增加了 子节点, 调用inflateWidget(newWidget, newSlot)生成一个新的Element返回

    如果之前的child不为null
    C、如果newWidget为null的话,说明这个位置需要移除以前的节点,调用 deactivateChild(child)移除并且返回null
    D、如果newWidget不为null的话,先调用Widget.canUpdate(child.widget, newWidget)对比是否能更新。这个方法会对比两个Widget的runtimeType和key,如果一致则说明子Widget没有改变,只是需要根据newWidget(配置清单)更新下当前节点的数据child.update(newWidget);如果不一致说明这个位置发生变化,则deactivateChild(child)后返回inflateWidget(newWidget, newSlot)

    如果一个页面是StatefulWidget,我们使用了setState来更新页面,并且没有显示的指定key,这里的setState会走child.update(newWidget);

    child.update(newWidget)

    update(covariant Widget newWidget)是一个抽象方法,不同element有不同实现,以StatulElement为例


    image.png

    这个方法调用了State的生命周期 didUpdateWidget,并在最后再次调用了rebuild(),再次走到performRebuild(),不断的递归直到页面的最子一级节点.

    • 注意这次调用rebuild()的已经不是PageState了,而是他的第一个子节点Scaffold。


      image.png

      所以,如果我们在一个较为复杂的页面中, 直接在页面节点调用setState()将会重新调用所有Widget(包括他们中的各种嵌套)的build()方法,会导致严重的性能损耗.

    仅为本人学习记录,您也有收获的话,点一点小心心哦~

    参考 https://blog.csdn.net/weixin_34221653/article/details/112267280
    Flutter渲染机制 https://juejin.cn/post/6974363413942108197#heading-6
    关于setState https://juejin.cn/post/6905996819445055495

    相关文章

      网友评论

        本文标题:flutter-状态管理1

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