摘要:但是接下来并不是讨论单线程如何方便开发,而是要深入的调度器,看一下是如何安排任务,调度工作。总结在大部分情况下,其实并不用担心会像游戏一样疯狂消耗电量,消耗电量表现应该跟原生没有多大差别。
开始
在原生开发中(例如Android)都会强调不能阻塞主线程,但是开发中经常会遇到发送请求或者操作数据库等,这些操作都会阻塞主线程,几乎唯一办法就是用多线程处理这些工作;而在Flutter中就像跟在前端一样,Dart也是单线程IO异步,刚才所说的这些操作既不会阻塞主线程也不会打断你的代码逻辑,所以在Flutter上开发有相当高的效率。
但是接下来并不是讨论单线程IO如何方便开发,而是要深入Flutter的Scheduler(调度器),看一下Flutter是如何安排任务,调度工作。
在Flutter中有几个调度阶段:
transientCallbacks
主要处理动画计算,动画状态的更新
midFrameMicrotasks
处理transientCallbacks阶段触发的Microtasks,啥是Microtasks?传送门
persistentCallbacks
主要处理build/layout/paint
postFrameCallbacks
主要在下一帧之前,做一些清理工作或者准备工作
idle
不产生Frame的空闲期,可以处理Tasks(由SchedulerBinding.scheduleTask触发),microtasks(由scheduleMicrotask触发),定时器的回调,响应事件处理(例如:用户的输入)
这个几个阶段是如何定义出来的尼?
在SchedulerBinding实例化的时候:
void initInstances() { super.initInstances(); _instance = this; ui.window.onBeginFrame = handleBeginFrame; ui.window.onDrawFrame = handleDrawFrame; }
可以看到底层暴露了两个阶段beginFrame和drawFrame,它们都是由底层触发的,一般跟屏幕的刷新速率一致,如果是60帧就是每16.7毫秒回调一次,而onDrawFrame回调是紧接着onBeginFrame回调的,因为刚才所提到Flutter有一个midFrameMicrotasks调度阶段然后结合Dart的消息循环机制,可以推断底层在Event队列中连续创建了两个Event,暂且称作:beginFrame事件和drawFrame事件。
在handleBeginFrame处理中:
void handleBeginFrame(Duration rawTimeStamp) { ... try { // TRANSIENT FRAME CALLBACKS Timeline.startSync("Animate", arguments: timelineWhitelistArguments); _schedulerPhase = SchedulerPhase.transientCallbacks; final Mapcallbacks = _transientCallbacks; _transientCallbacks = {}; callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) { if (!_removedIds.contains(id)) _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack); }); _removedIds.clear(); } finally { _schedulerPhase = SchedulerPhase.midFrameMicrotasks; } }
很简单遍历_transientCallbacks列表,然后回调,最后就转入midFrameMicrotasks阶段;而把回调加入_transientCallbacks列表的方法,跟前端的requestAnimationFrame方法几乎一样,调用scheduleFrameCallback方法然后会返回一个id,你也可以使用cancelFrameCallbackWithId来取消这次回调。
接着进入handleDrawFrame方法:
void handleDrawFrame() { Timeline.finishSync(); // end the "Animate" phase try { // PERSISTENT FRAME CALLBACKS _schedulerPhase = SchedulerPhase.persistentCallbacks; for (FrameCallback callback in _persistentCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp); // POST-FRAME CALLBACKS _schedulerPhase = SchedulerPhase.postFrameCallbacks; final ListlocalPostFrameCallbacks = new List .from(_postFrameCallbacks); _postFrameCallbacks.clear(); for (FrameCallback callback in localPostFrameCallbacks) _invokeFrameCallback(callback, _currentFrameTimeStamp); } finally { _schedulerPhase = SchedulerPhase.idle; Timeline.finishSync(); // end the Frame _currentFrameTimeStamp = null; } // All frame-related callbacks have been executed. Run lower-priority tasks. _runTasks(); }
直接进入persistentCallbacks阶段,drawFrame方法会在这里回调(build/layout/paint),然后在布局绘制完成后紧接着就进入postFrameCallbacks阶段,在这个阶段我们基本可以拿到最新的布局信息了,就像Vue的$nextTick方法一样,最后就是idle阶段,这里的默认处理就有点意思了。
直接来到_runTask方法:
void _runTasks() { if (_taskQueue.isEmpty || locked) return; final _TaskEntry entry = _taskQueue.first; if (schedulingStrategy(priority: entry.priority, scheduler: this)) { try { (_taskQueue.removeFirst().task)(); } finally { if (_taskQueue.isNotEmpty) _ensureEventLoopCallback(); } } else { scheduleFrame(); } }
刚才也提到可以使用SchedulerBinding.scheduleTask加入一个task,但是task执行前想要执行首先要判断优先级,默认的判断是这样的:
bool defaultSchedulingStrategy({ int priority, SchedulerBinding scheduler }) { if (scheduler.transientCallbackCount > 0) return priority >= Priority.animation.value; return true; }
也就是transientCallback存在,而且task的优先级不大于animation的优先级,那么task就不会执行了。其实目标应该是为了保证动画足够流畅,因为transientCallback一般都是处理动画的,如果存在transientCallback一般就是当前有正在播放的动画,所以_runTasks方法会立马进行第二帧的调度,动画得以流畅进行。
大部分时候,等动画播放完再处理一些耗时的操作其实也并不是问题,问题是如果存在循环播放的动画就有点尴尬了,这样task就会永远都没机会执行,这是一个值得注意的地方,要么就是修改默认的调度策略,要么把安排第二次播放动画的代码放到addPostFrameCallback里面并使用scheduleMicrotask触发,这样的话在处理完一个Task之后,又可以触发第二次动画,把影响降到最低。
在schedulingStrategy方法之后,就是_ensureEventLoopCallback:
void _ensureEventLoopCallback() { assert(!locked); if (_hasRequestedAnEventLoopCallback) return; Timer.run(handleEventLoopCallback); _hasRequestedAnEventLoopCallback = true; }
主要驱动事件循环,其实在scheduleTask方法里面也会调用这个方法,保证task队列里面的task都可以得到处理:
void scheduleTask(VoidCallback task, Priority priority) { final bool isFirstTask = _taskQueue.isEmpty; _taskQueue.add(new _TaskEntry(task, priority.value)); if (isFirstTask && !locked) _ensureEventLoopCallback(); }
这里可以得知Flutter并不是都在以每16.7毫秒产生一帧来布局绘制界面,当没有动画,或者我们不调起setState方法,又或者说不调起ScheduleBinding.scheduleFrame有关联的方法,Flutter并不会进行布局绘制和刷新界面,这样的情况下就不能靠onBeginFrame和onDrawFrame来驱动处理task,只能靠dart自身的事件循环,这也是_ensureEventLoopCallback方法存在的必要性。
总结在大部分情况下,其实并不用担心Flutter会像游戏一样疯狂消耗电量,消耗电量表现应该跟原生没有多大差别。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/89633.html
摘要:但是好像反其道而行之,样式糅合在结构里面,这样究竟有啥意思尼首先应该是一个性能的考虑,浏览器解析其实也是一个性能消耗点,没有解析自然也可以加快页面的显示。 开始 搞前端的同学可能都习惯了CSS局部的思维,过去也出现过一些跟布局或者样式相关的标签,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已经不推荐使用。但是在Flutter里...
摘要:但是好像反其道而行之,样式糅合在结构里面,这样究竟有啥意思尼首先应该是一个性能的考虑,浏览器解析其实也是一个性能消耗点,没有解析自然也可以加快页面的显示。 开始 搞前端的同学可能都习惯了CSS局部的思维,过去也出现过一些跟布局或者样式相关的标签,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已经不推荐使用。但是在Flutter里...
摘要:开始继续接着分析相关的样式和布局控件,但是这次内容难度感觉比较高,怕有分析不到位的地方,所以这次仅仅当做一个参考,大家最好可以自己阅读一下代码,应该会有更深的体会。关于属性,指前一个组件的布局区域和绘制区域重叠了。 开始 继续接着分析Flutter相关的样式和布局控件,但是这次内容难度感觉比较高,怕有分析不到位的地方,所以这次仅仅当做一个参考,大家最好可以自己阅读一下代码,应该会有更深...
摘要:开始继续接着分析相关的样式和布局控件,但是这次内容难度感觉比较高,怕有分析不到位的地方,所以这次仅仅当做一个参考,大家最好可以自己阅读一下代码,应该会有更深的体会。关于属性,指前一个组件的布局区域和绘制区域重叠了。 开始 继续接着分析Flutter相关的样式和布局控件,但是这次内容难度感觉比较高,怕有分析不到位的地方,所以这次仅仅当做一个参考,大家最好可以自己阅读一下代码,应该会有更深...
阅读 3516·2023-04-25 17:35
阅读 2589·2021-11-24 09:39
阅读 2530·2021-10-18 13:32
阅读 3413·2021-10-11 10:58
阅读 1635·2021-09-26 09:55
阅读 6146·2021-09-22 15:47
阅读 965·2021-08-26 14:15
阅读 3470·2019-08-30 15:55