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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容