Flutter 啟動流程

說起啟動那必須從main方法開始呀

void main() => runApp(MyApp());
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

runApp的代碼很簡單遮晚,傳遞一個Widget參數腊徙,然后執(zhí)行了三行代碼惨寿,三行代碼代表了Flutter App啟動的主要三個流程:

  1. binding初始化(ensureInitialized)
  2. 綁定根節(jié)點(scheduleAttachRootWidget)
  3. 繪制熱身幀(scheduleWarmUpFrame)

binding初始化(ensureInitialized)

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

mixin我們前面已經介紹過了,不難發(fā)現 WidgetsFlutterBinding 單例過程其實也是對7個mixin的binding的初始化 和橙。
WidgetsFlutterBinding繼承了BindingBase,在調用自己的構造器之前會先先執(zhí)行了父類BindingBase構造函數舟误。

  BindingBase() {
    developer.Timeline.startSync('Framework initialization');

    assert(!_debugInitialized);
    initInstances();
    assert(_debugInitialized);

    assert(!_debugServiceExtensionsRegistered);
    initServiceExtensions();
    assert(_debugServiceExtensionsRegistered);

    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});

    developer.Timeline.finishSync();
  }

WidgetsBinding 覆蓋了前面的 binding 的 initInstances()葡秒,所以 WidgetsBinding 的 initInstances() 會首先被調用,而 WidgetsBinding 的 initInstances 函數中先通過 super 向上調用 initInstances ,根據mixin的特性 執(zhí)行順序: initInstances 的執(zhí)行順序依次是:BindingBase -> GestureBinding -> SchedulerBinding -> ServicesBinding -> PaintingBinding -> SemanticsBinding -> RendererBinding -> WidgetsBinding眯牧,從而依次完成各個 Binding 的初始化相關工作蹋岩。

RendererBinding

mixin RendererBinding on BindingBase,   
{

@override
void initInstance() {
super.initInstance();
_pipelineOwner = PipelineOwner(
……………
);
……………
initRenderView(){
renderView = RenderView(
configuration:createViewConfiguration(),
window:window
);
……………
}
}

}
}

PipelineOwner是渲染管道,它在widget渲染的階段有重要的作用学少,具體的會在后續(xù)的渲染繪制會著重分析.
RenderView 就是我們屏幕真實顯示的那個View剪个,Flutter是單頁面的UI框架,renderView就是這個時間點被初始化出來的版确。它是Render tree 的根節(jié)點扣囊,同時將當前設備的物理屏幕信息配置上。

SemanticsBinding

渲染輔助類綁定绒疗,主要負責關聯語義樹與Flutter Engine侵歇。Flutter維護了一個 semantic tree(語義樹),頁面構建的時候會根據各Widget的語義描述構建一棵 semantic tree吓蘑。如在Image組件中配置 semanticLabel 語義內容惕虑,用戶在IOS/Android手機開啟無障礙功能時,觸摸到該 Image 時通過語義樹查找到對應的語義描述交給Flutter Engine士修,實現讀屏等功能枷遂。對應Semantics widget樱衷,給子Widget定義語義棋嘲,一些安卓自動化無法識別flutter上的widget,通過Semantics 定義語義就可以識別出來了

PaintingBinding

監(jiān)聽系統(tǒng)字體變化事件

ServicesBinding

  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    
    // 構建一個用于platform與flutter層通信的 BinaryMessenger
    _defaultBinaryMessenger = createBinaryMessenger();
    
    // 設置window監(jiān)聽回調矩桂,處理platform發(fā)送的消息
    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
    initLicenses();
    
    // 設置處理platform發(fā)送的系統(tǒng)消息的 Handler
    SystemChannels.system.setMessageHandler(handleSystemMessage);
    
    // 設置AppLifecycleState生命周期回調
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    
    // AppLifecycleState 為 resumed 和 inactive 時才允許響應Vsync信號進行繪制
    readInitialLifecycleStateFromNativeWindow();
  }

ServicesBinding的初始化的工作主要是兩個:

  • platform與flutter層通信相關服務的初始化(比如讀取asset內的資源就是通過defaultBinaryMessenger 也是內部默認的channel使用的消息對象 )
  • 注冊監(jiān)聽了flutter app的生命周期變化事件沸移,根據生命周期狀態(tài)決定是否允許發(fā)起繪制任務

SchedulerBinding

繪制調度綁定,繪制渲染流程會詳細分析,也可關注文后的繪制流程時序圖

GestureBinding

手勢事件綁定,主要處理觸屏幕指針事件的分發(fā)以及事件最終回調處理侄榴。

綁定根節(jié)點(scheduleAttachRootWidget)

  @protected
  void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
      // 將傳入的Widget綁定RenderView根節(jié)點上
      attachRootWidget(rootWidget);
    });
  }

attachRootWidget源碼:

  void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
  }

先是通過傳入的 rootWidget 及 RenderView 實例化了一個RenderObjectToWidgetAdapter對象雹锣,而RenderObjectToWidgetAdapter是繼承自RenderObjectWidget,即創(chuàng)建了Widget樹的根節(jié)點癞蚕。繼續(xù)調用 attachToRenderTree:

  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
    if (element == null) {
      owner.lockState(() {
      
        // 創(chuàng)建了一個RenderObjectToWidgetElement實例作為element tree的根節(jié)點
        element = createElement();
        assert(element != null);
        
        // 綁定BuildOwner
        element.assignOwner(owner);
      });
      
      // 標記需要構建的element蕊爵,并rebuild
      owner.buildScope(element, () {
        element.mount(null, null);
      });

      SchedulerBinding.instance.ensureVisualUpdate();
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element;
  }
@override
void mount(Element parent, dynamic newSlot){
………
_rebuild();
} 



void _rebuild(){
………
_child = updateChild(_child, widget.child. _rootChildSlot);
………
}

}
@override
  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

attachToRenderTree 中通過 createElement() 創(chuàng)建了一個RenderObjectToWidgetElement 實例作為 element tree 的根節(jié)點,并綁定BuildOwner桦山,通過 BuildOwner 構建需要構建的 element攒射。
在mount函數這里會觸發(fā)_rebuild();在_rebuild()里面我們看到了updateChild,updateChildl里有調用inflateWidget方法,inflateWidget這個函數恒水,在這個函數里面就會觸發(fā)StatelessWidget和StatefulWidget的 createElement去創(chuàng)建element ,element又會去調用對應類的mount函數会放,經過一系列的流程之后,又會回到inflateWidget這個函數中钉凌,再次觸發(fā)新的mount函數咧最,形成一個層層調用,不斷創(chuàng)建parentElement到childElement的過程,這個過程完成了element tree的構建。

繪制熱身幀(scheduleWarmUpFrame)

  void scheduleWarmUpFrame() {
    if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
      return;

    _warmUpFrame = true;
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
    Timer.run(() {
      assert(_warmUpFrame);
      handleBeginFrame(null);
    });
    Timer.run(() {
      assert(_warmUpFrame);
      handleDrawFrame();
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });

    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }

熱身幀是通過handleBeginFrame矢沿、handleDrawFrame這兩個回調來進行繪制流程滥搭。handleBeginFrame處理動畫相關邏輯,動畫回調后并不立即執(zhí)行動畫捣鲸,而是改變了animation.value论熙,并調用setSate()來發(fā)起繪制請求。動畫的過程就是在 Vsync 信號到來時根據動畫進度計算出對應的 value摄狱,而對應的 Widget 也會隨著 animation.value 的變化而重建脓诡,從而形成動畫,和Android的屬性動畫原理差不多媒役。handleBeginFrame處理完后祝谚,會優(yōu)先處理microTask任務隊列。然后才是event Task酣衷,window.onDrawFrame()交惯,對應SchedulerBinding.handleDrawFrame()。(Timer任務會加入到event queue穿仪,flutter的事件處理機制是優(yōu)先處理 micro queue 中任務)

  void handleDrawFrame() {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
    Timeline.finishSync(); // end the "Animate" phase
    try {
      // 處理Persistent類型回調,主要包括build\layout\draw流程
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks)
        // 注釋1
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      // 處理Post-Frame回調席爽,主要是狀態(tài)清理,準備調度下一幀繪制請求
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    } finally {
    
      // 處理完成啊片,設置狀態(tài)為idle
      _schedulerPhase = SchedulerPhase.idle;
      Timeline.finishSync(); // end the Frame
      assert(() {
        if (debugPrintEndFrameBanner)
          debugPrint('?' * _debugBanner.length);
        _debugBanner = null;
        return true;
      }());
      _currentFrameTimeStamp = null;
    }
  }
  // RendererBinding
  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    _mouseTracker.schedulePostFrameCheck();
  }

  void drawFrame() {
    assert(renderView != null);
    // 布局
    pipelineOwner.flushLayout();
    // 更新 RenderObject 中需要繪制的內容
    pipelineOwner.flushCompositingBits();
    // 繪制
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      // 產生這一幀的數據Scene只锻,由window.render交給Engine,最終顯示到屏幕(發(fā)送數據到GPU)紫谷。
      renderView.compositeFrame();
      // 將語義樹發(fā)送到操作系統(tǒng)
      pipelineOwner.flushSemantics();
      _firstFrameSent = true;
    }
  }
// WidgetsBinding
void drawFrame() {
   ...
   
   try {
    if (renderViewElement != null)
      //調用BuildOwner.buildScope開始構建
      buildOwner.buildScope(renderViewElement);
      
    //調用RendererBinding.drawFrame齐饮,開始布局、繪制階段笤昨。
    super.drawFrame();
    
    //從element tree中移除不需要的element祖驱,unmount
    buildOwner.finalizeTree();
  } finally {
     ...
  }
}
繪制時序.png

SchedulerBinding.scheduleWarmUpFrame
-> SchedulerBinding.handleBeginFrame 處理動畫
-> SchedulerBinding.handleDrawFrame
-----> WidgetBinding.drawFrame 通過 buildOwner 構建組件
-----> RendererBinding.drawFrame 通過 pipelineOwner 完成組件布局和繪制
-----> renderView.compositeFrame 發(fā)送 Scene 到GPU

啟動調用時序.png

歡迎拍磚,交流瞒窒!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末捺僻,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子崇裁,更是在濱河造成了極大的恐慌匕坯,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寇壳,死亡現場離奇詭異醒颖,居然都是意外死亡,警方通過查閱死者的電腦和手機壳炎,發(fā)現死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門泞歉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逼侦,“玉大人,你說我怎么就攤上這事腰耙¢欢” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵挺庞,是天一觀的道長晰赞。 經常有香客問我,道長选侨,這世上最難降的妖魔是什么掖鱼? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮援制,結果婚禮上戏挡,老公的妹妹穿的比我還像新娘。我一直安慰自己晨仑,他們只是感情好褐墅,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著洪己,像睡著了一般妥凳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上答捕,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天逝钥,我揣著相機與錄音,去河邊找鬼噪珊。 笑死晌缘,一個胖子當著我的面吹牛齐莲,可吹牛的內容都是我干的痢站。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼选酗,長吁一口氣:“原來是場噩夢啊……” “哼阵难!你這毒婦竟也來了?” 一聲冷哼從身側響起芒填,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤呜叫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后殿衰,有當地人在樹林里發(fā)現了一具尸體朱庆,經...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年闷祥,在試婚紗的時候發(fā)現自己被綠了娱颊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖箱硕,靈堂內的尸體忽然破棺而出拴竹,到底是詐尸還是另有隱情,我是刑警寧澤剧罩,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布栓拜,位于F島的核電站,受9級特大地震影響惠昔,放射性物質發(fā)生泄漏幕与。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一镇防、第九天 我趴在偏房一處隱蔽的房頂上張望纽门。 院中可真熱鬧,春花似錦营罢、人聲如沸赏陵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蝙搔。三九已至,卻和暖如春考传,著一層夾襖步出監(jiān)牢的瞬間吃型,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工僚楞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留勤晚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓泉褐,卻偏偏與公主長得像赐写,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子膜赃,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354