flutter-状态管理1

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

原生开发的同学可能对"状态"没有什么概念,因为原生开发大多使用命令式框架.比如: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)