摘要:但是接下來并不是討論單線程如何方便開發(fā),而是要深入的調(diào)度器,看一下是如何安排任務(wù),調(diào)度工作??偨Y(jié)在大部分情況下,其實(shí)并不用擔(dān)心會(huì)像游戲一樣瘋狂消耗電量,消耗電量表現(xiàn)應(yīng)該跟原生沒有多大差別。
開始
在原生開發(fā)中(例如Android)都會(huì)強(qiáng)調(diào)不能阻塞主線程,但是開發(fā)中經(jīng)常會(huì)遇到發(fā)送請(qǐng)求或者操作數(shù)據(jù)庫等,這些操作都會(huì)阻塞主線程,幾乎唯一辦法就是用多線程處理這些工作;而在Flutter中就像跟在前端一樣,Dart也是單線程IO異步,剛才所說的這些操作既不會(huì)阻塞主線程也不會(huì)打斷你的代碼邏輯,所以在Flutter上開發(fā)有相當(dāng)高的效率。
但是接下來并不是討論單線程IO如何方便開發(fā),而是要深入Flutter的Scheduler(調(diào)度器),看一下Flutter是如何安排任務(wù),調(diào)度工作。
在Flutter中有幾個(gè)調(diào)度階段:
transientCallbacks
主要處理動(dòng)畫計(jì)算,動(dòng)畫狀態(tài)的更新
midFrameMicrotasks
處理transientCallbacks階段觸發(fā)的Microtasks,啥是Microtasks?傳送門
persistentCallbacks
主要處理build/layout/paint
postFrameCallbacks
主要在下一幀之前,做一些清理工作或者準(zhǔn)備工作
idle
不產(chǎn)生Frame的空閑期,可以處理Tasks(由SchedulerBinding.scheduleTask觸發(fā)),microtasks(由scheduleMicrotask觸發(fā)),定時(shí)器的回調(diào),響應(yīng)事件處理(例如:用戶的輸入)
這個(gè)幾個(gè)階段是如何定義出來的尼?
在SchedulerBinding實(shí)例化的時(shí)候:
void initInstances() { super.initInstances(); _instance = this; ui.window.onBeginFrame = handleBeginFrame; ui.window.onDrawFrame = handleDrawFrame; }
可以看到底層暴露了兩個(gè)階段beginFrame和drawFrame,它們都是由底層觸發(fā)的,一般跟屏幕的刷新速率一致,如果是60幀就是每16.7毫秒回調(diào)一次,而onDrawFrame回調(diào)是緊接著onBeginFrame回調(diào)的,因?yàn)閯偛潘岬紽lutter有一個(gè)midFrameMicrotasks調(diào)度階段然后結(jié)合Dart的消息循環(huán)機(jī)制,可以推斷底層在Event隊(duì)列中連續(xù)創(chuàng)建了兩個(gè)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列表,然后回調(diào),最后就轉(zhuǎn)入midFrameMicrotasks階段;而把回調(diào)加入_transientCallbacks列表的方法,跟前端的requestAnimationFrame方法幾乎一樣,調(diào)用scheduleFrameCallback方法然后會(huì)返回一個(gè)id,你也可以使用cancelFrameCallbackWithId來取消這次回調(diào)。
接著進(jìn)入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(); }
直接進(jìn)入persistentCallbacks階段,drawFrame方法會(huì)在這里回調(diào)(build/layout/paint),然后在布局繪制完成后緊接著就進(jìn)入postFrameCallbacks階段,在這個(gè)階段我們基本可以拿到最新的布局信息了,就像Vue的$nextTick方法一樣,最后就是idle階段,這里的默認(rèn)處理就有點(diǎn)意思了。
直接來到_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加入一個(gè)task,但是task執(zhí)行前想要執(zhí)行首先要判斷優(yōu)先級(jí),默認(rèn)的判斷是這樣的:
bool defaultSchedulingStrategy({ int priority, SchedulerBinding scheduler }) { if (scheduler.transientCallbackCount > 0) return priority >= Priority.animation.value; return true; }
也就是transientCallback存在,而且task的優(yōu)先級(jí)不大于animation的優(yōu)先級(jí),那么task就不會(huì)執(zhí)行了。其實(shí)目標(biāo)應(yīng)該是為了保證動(dòng)畫足夠流暢,因?yàn)閠ransientCallback一般都是處理動(dòng)畫的,如果存在transientCallback一般就是當(dāng)前有正在播放的動(dòng)畫,所以_runTasks方法會(huì)立馬進(jìn)行第二幀的調(diào)度,動(dòng)畫得以流暢進(jìn)行。
大部分時(shí)候,等動(dòng)畫播放完再處理一些耗時(shí)的操作其實(shí)也并不是問題,問題是如果存在循環(huán)播放的動(dòng)畫就有點(diǎn)尷尬了,這樣task就會(huì)永遠(yuǎn)都沒機(jī)會(huì)執(zhí)行,這是一個(gè)值得注意的地方,要么就是修改默認(rèn)的調(diào)度策略,要么把安排第二次播放動(dòng)畫的代碼放到addPostFrameCallback里面并使用scheduleMicrotask觸發(fā),這樣的話在處理完一個(gè)Task之后,又可以觸發(fā)第二次動(dòng)畫,把影響降到最低。
在schedulingStrategy方法之后,就是_ensureEventLoopCallback:
void _ensureEventLoopCallback() { assert(!locked); if (_hasRequestedAnEventLoopCallback) return; Timer.run(handleEventLoopCallback); _hasRequestedAnEventLoopCallback = true; }
主要驅(qū)動(dòng)事件循環(huán),其實(shí)在scheduleTask方法里面也會(huì)調(diào)用這個(gè)方法,保證task隊(duì)列里面的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毫秒產(chǎn)生一幀來布局繪制界面,當(dāng)沒有動(dòng)畫,或者我們不調(diào)起setState方法,又或者說不調(diào)起ScheduleBinding.scheduleFrame有關(guān)聯(lián)的方法,F(xiàn)lutter并不會(huì)進(jìn)行布局繪制和刷新界面,這樣的情況下就不能靠onBeginFrame和onDrawFrame來驅(qū)動(dòng)處理task,只能靠dart自身的事件循環(huán),這也是_ensureEventLoopCallback方法存在的必要性。
總結(jié)在大部分情況下,其實(shí)并不用擔(dān)心Flutter會(huì)像游戲一樣瘋狂消耗電量,消耗電量表現(xiàn)應(yīng)該跟原生沒有多大差別。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/89633.html
摘要:但是好像反其道而行之,樣式糅合在結(jié)構(gòu)里面,這樣究竟有啥意思尼首先應(yīng)該是一個(gè)性能的考慮,瀏覽器解析其實(shí)也是一個(gè)性能消耗點(diǎn),沒有解析自然也可以加快頁面的顯示。 開始 搞前端的同學(xué)可能都習(xí)慣了CSS局部的思維,過去也出現(xiàn)過一些跟布局或者樣式相關(guān)的標(biāo)簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經(jīng)不推薦使用。但是在Flutter里...
摘要:但是好像反其道而行之,樣式糅合在結(jié)構(gòu)里面,這樣究竟有啥意思尼首先應(yīng)該是一個(gè)性能的考慮,瀏覽器解析其實(shí)也是一個(gè)性能消耗點(diǎn),沒有解析自然也可以加快頁面的顯示。 開始 搞前端的同學(xué)可能都習(xí)慣了CSS局部的思維,過去也出現(xiàn)過一些跟布局或者樣式相關(guān)的標(biāo)簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經(jīng)不推薦使用。但是在Flutter里...
摘要:開始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深的體會(huì)。關(guān)于屬性,指前一個(gè)組件的布局區(qū)域和繪制區(qū)域重疊了。 開始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深...
摘要:開始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深的體會(huì)。關(guān)于屬性,指前一個(gè)組件的布局區(qū)域和繪制區(qū)域重疊了。 開始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深...
閱讀 3526·2023-04-25 17:35
閱讀 2599·2021-11-24 09:39
閱讀 2538·2021-10-18 13:32
閱讀 3424·2021-10-11 10:58
閱讀 1642·2021-09-26 09:55
閱讀 6176·2021-09-22 15:47
閱讀 972·2021-08-26 14:15
閱讀 3476·2019-08-30 15:55