Flutter----探索setState原理

简要说明State

1. State是什么?

我们都知道,Flutter有个特性**“everything is widget”,而Widget@immutable(不可变的),那么你就会问,我每次setState之后的Widget**树是怎么渲染的?他既然是不变的那怎么会进行重绘改变呢?那么疑问自然而然的就来了!!!!

我们知道,StatefulWidgetState会帮助我们实现Widget重绘,也就是我们需要重绘的时候,还是会通过State去实现重绘但是他不是@immutable的吗?很乱对吧,往下看!

2. State是怎么实现跨帧的

说到这里就涉及到了Flutter中Widget的原理,这里我不在赘述,我在后面有文章去讲解到Widget,Element,RenderObject之间的联系。这里就多不说了,只说一下原理就好,一般我们通过State进行操作,就是讲State保存在Element对象中,这样每次调用build()时,斗会通过State.build(this);得到新的Widget。这个就是State简单的实现!

上面简要的说明一下State,这里我们说一下我们的主要内容setState(() {});

我们都应该知道Flutter的状态类就分两类,一个是有状态的StatefulWidget;一个是无状态的StatelessWidget

那么自然我们也会想到只有有状态的StatefulWidget会有setState这个方法,相应的无状态的就没有!

在说之前,我相信大家开始接触和使用Flutter的时候会碰到过这样的问题,总体来说内存泄漏!

 E/flutter: [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: setState() called after dispose(): 
 _SystemMessageListRouteState#017f6(lifecycle state: defunct, not mounted)
 
    This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can 
    occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop 
    listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before 
    calling setState() to ensure the object is still in the tree.
    This error might indicate a memory leak if setState() is being 
    called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid 
    memory leaks, consider breaking the reference to this object during dispose().

不出意外我们的解决方法都是一样的把

if(mounted){
  //要改变的内容
  setState(() {});
}

我们都知道在调用setState(() {});之前必须是没有调用过dispose()方法的把,不然会出问题,有的时候你会发现他就算是有这个问题也不影响功能是把,但是还是可能会出错(我不知道什么错,因为我有强迫症控制台有点乱东西我就难受),所以我们会通过mounted属性来判断这个方法是不是合理的。

我们看一下setState方法的源码!

 @protected
  void setState(VoidCallback fn) {
    ...
    //中间是一些assert()我就不多说了,
    _element.markNeedsBuild();
  }

最终setState除了哪一些assert()条件判断就是最后这个 _element.markNeedsBuild();,在这里它会自己进行标记需要重绘重构的Widget,然后怎么办呢?点进去继续看!!!!

markNeedsBuild()方法

我们把那些assert()折叠

这里面的东西我就在多说一个

if (!_active)
      return;//返回

这个很有用,由于我们一帧做两次更新有点低效,那么会有这个判断,false的时候直接返回!

if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);

还是继续看这边正常的,我们在这里面的最主要方法,他在这里会将element元素标记为**“脏”,然后会把他放到全局的“脏”列表**里面,以便在下一帧的更新信号有时更新。

补充:这里的“脏”仅仅是待更新的列表

还是老规矩继续看scheduleBuildFor()方法。

scheduleBuildFor()方法

该方法会把一个element添加到_dirtyElements链表中,主要是为了方便当WidgetsBinding.drawFrame中调用buildScope的时候能够重构element。而onBuildScheduled()是一个 BuildOwner 的回调。

在该方法的上面有说明

/// Adds an element to the dirty elements list so that it will be rebuilt

  /// when [WidgetsBinding.drawFrame] calls [buildScope].

onBuildScheduled()方法回调会在WidgetsBindinginitInstances初始化。源码如下:

就是我鼠标选中的这行。可以看到Flutter初始化WidgetsBinding时,_handleBuildScheduled就赋值给了buildOwner.onBuildScheduled这个方法。

继续看这个_handleBuildScheduled方法:

很简单字面意思就是确保视觉刷新。我们继续走下去!

ensureVisualUpdate()方法

源码上面有具体的说明,我这里就不过多的翻译了!

我们可以看到这块代码从上面的源码(在底层源码中没截图出来,可以自己去看),schedulerPhase的初始值为SchedulerPhase.idle。而且schedulerPhase必然是一个枚举类型,看到代码应该知道他有五个枚举值!

状态含义
idle没有正在处理的帧,可能正在实行的是WidgetsBinding.scheduleTask,scheduleMicrotask,Timer,handlers,或者其他回调等
postFrameCallbacks主要是清理和执行下一帧的工作
transientCallbacksSchedulerBinding.handleBeginFrame 过程, 处理动画状态更新
midFrameMicrotasks处理 transientCallbacks 阶段触发的微任务(Microtasks)
persistentCallbacksWidgetsBinding.drawFrame 和 SchedulerBinding.handleDrawFrame 过程,build/layout/paint 工作

补充:我们又引出了一个平时可能用到的方法,postFrameCallbacks,这个可以在initstate的前一帧可回调的方法(当然这是个人理解,有问题大佬可以纠正),使用的方式大多数是隐私政策的弹出吧!

还是继续走,看一下第二个case调用的scheduleFrame();

简单说一下代码的意义

如果需要,可以通过调用 [Window.scheduleFrame]来安排新框架。
在此之后,引擎将(最终)调用[handleBeginFrame]。 
(此调用可能会延迟,例如,如果设备屏幕关闭,则通常会延迟直到屏幕打开并且应用程序可见为止。)
在一个框架中调用此强制另一个框架即使当前帧尚未完成也要进行调度。
当操作系统提供的“ Vsync”信号触发时,计划的帧将得到服务。
在过去硬件在显示器的更新之间垂直移动电子束时,“ Vsync”信号或垂直同步信号//过去一直与显示器刷新有关。
现代硬件的操作稍微复杂些,但是概念上的“ Vsync”刷新信号继续用于指示应用程序何时应更新其渲染。
要在此函数计划框架时将堆栈跟踪打印到控制台,请将[debugPrintScheduleFrameStacks]设置为true。
另请参见:* [scheduleForcedFrame],它在安排帧时会忽略[lifecycleState]。
 * [scheduleWarmUpFrame],它会完全忽略“ Vsync”信号,并且立即触发一个帧。

好吧抱歉我实在英语不咋地还是靠翻译吧!

然后接着看window.scheduleFrame()方法。

这个方法是一个Native()方法,由于本人实在没有过这样的编程经验,有兴趣的可以去看看袁辉辉大佬的讲解!在这我就不过多的赘述了。看过之后就会对这个过程了解了!
这里是袁辉辉大佬的著作,想了解更详细的可以去看看!!!

最终我们得到了这个过程!

有点复杂是吗?其实不复杂这样一整理就会发现很简单!

条件判断----mounted

  • 生命周期的判断(不能再setState之前调用dispose)
  • 是否可以刷新(if(mounted){setState(() {});}

添加到脏列表(_dirty = true)

  • 脏列表是待更新的列表
  • _dirty 为true时添加,false是返回

管理类

  • 让管理类方法知道自己需要被重新构建
  • 调用owner.scheduleBuildFor(this)

调用window.scheduleFrame()方法----这是一个Native方法

更新UI

  • WidgetsBinding.drawFrame中调用buildScope重构element
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值