摘要:但是接下來并不是討論單線程如何方便開發,而是要深入的調度器,看一下是如何安排任務,調度工作。總結在大部分情況下,其實并不用擔心會像游戲一樣瘋狂消耗電量,消耗電量表現應該跟原生沒有多大差別。
開始
在原生開發中(例如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會像游戲一樣瘋狂消耗電量,消耗電量表現應該跟原生沒有多大差別。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/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相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深...
閱讀 3514·2023-04-25 17:35
閱讀 2588·2021-11-24 09:39
閱讀 2525·2021-10-18 13:32
閱讀 3409·2021-10-11 10:58
閱讀 1630·2021-09-26 09:55
閱讀 6134·2021-09-22 15:47
閱讀 959·2021-08-26 14:15
閱讀 3467·2019-08-30 15:55