【Flutter原理】入口runApp源碼分析

    Flutter APP跟其他應(yīng)用程序一樣,入口也是在main方法涣狗,其內(nèi)部就是一行代碼,調(diào)用runAppp方法穗熬,將我們定義的根widget添加到界面上唤蔗。作為Flutter的入口函數(shù)窟赏,我們有必要了解其背后的工作原理涯穷,runApp都做了些啥,能讓我們的widget顯示在界面上作煌,同事支持各種事件操作粟誓,界面刷新等起意。

首先我們來(lái)看看runApp源碼:

// App是一個(gè)widget揽咕,是Flutter應(yīng)用啟動(dòng)以后要展示的第一個(gè)組件
void runApp(Widget app) {
    // 1. 確保WidgetsFlutterBinding被初始化心褐。
  WidgetsFlutterBinding.ensureInitialized()
    // 2. 將傳遞過(guò)來(lái)的根widget app attach到某個(gè)地方
    ..scheduleAttachRootWidget(app)
    // 3. 調(diào)度一個(gè)‘熱身’幀
    ..scheduleWarmUpFrame();
}

接下來(lái)我們就繼續(xù)對(duì)著runApp內(nèi)三行代碼進(jìn)行逐一突破:

1笼踩、WidgetsFlutterBinding初始化

直接看ensureInitialized()源碼:

// WidgetsFlutterBinding可以理解為是widget框架和Flutter引擎的橋梁
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance!;
  }
}

WidgetsFlutterBinding類(lèi)繼承自BindingBase并且混入[mixin]了很多其他Binding類(lèi)嚎于,看名稱(chēng)都是綁定各種不同的功能于购;

BindingBase肋僧,上面的各個(gè)mixin Binding類(lèi)都是繼承自它控淡,各個(gè)mixin類(lèi)都重寫(xiě)了initInstances()方法掺炭,并且調(diào)用了super.initInstances()凭戴,所以他們所有的initInstans()方法都會(huì)被串行順序執(zhí)行么夫。如果對(duì)mixin機(jī)制不是很理解可以先看看”小白都能看懂的關(guān)于Mixins機(jī)制的理解“。最終FlutterWidgetBinding()初始化的邏輯為:

WidgetsFlutterBinding.ensureInitialized

WidgetsFlutterBinding經(jīng)過(guò)mixin依賴(lài),實(shí)現(xiàn)了所有的Binding類(lèi)的功能拜银,下面逐一大概介紹一下每個(gè)Binding的作用:

  1. GestureBinding:提供了window.onPointerDataPacket的回調(diào)遭垛,綁定Fragmework子系統(tǒng)锯仪,是Framework事件模型與底層事件的綁定入口庶喜。
  2. ServicesBinding:提供了window.onPlatformMessage回調(diào),用于綁定平臺(tái)消息通道(messagechannel)秩冈,主要處理原生和Flutter之間的通信斥扛。
  3. SchedulerBinding:提供了window.onBeginFrame和window.onDrawFrame回調(diào)入问,監(jiān)聽(tīng)刷新事件,綁定Framework繪制調(diào)度子系統(tǒng)
  4. PaintingBinding:綁定繪制庫(kù)稀颁,主要用于處理圖片緩存芬失。
  5. SematicsBinding:語(yǔ)義化層與Flutter engine的橋梁,主要是輔助功能的底層支持匾灶。
  6. RenderBinding:提供了window.onMetricsChange棱烂、window.onTextScaleFactorChanged等回調(diào)。它是渲染樹(shù)與Flutter engine的橋梁阶女。
  7. WidgetsBinding:提供了window.onLocaleChanged颊糜,onBuildScheduled等回調(diào)。它是Flutter widget層與engine的橋梁衬鱼。

很明顯锚扎,可以看到Window類(lèi)提供了各種平臺(tái)的回調(diào)方法,正是我們Flutter Framework連接宿主操作系統(tǒng)的接口馁启。我們來(lái)大致看下源碼:

class Window {
    
  // 當(dāng)前設(shè)備的DPI驾孔,即一個(gè)邏輯像素顯示多少物理像素,數(shù)字越大惯疙,顯示效果就越精細(xì)保真翠勉。
  // DPI是設(shè)備屏幕的固件屬性,如Nexus 6的屏幕DPI為3.5 
  double get devicePixelRatio => _devicePixelRatio;
  
  // Flutter UI繪制區(qū)域的大小
  Size get physicalSize => _physicalSize;

  // 當(dāng)前系統(tǒng)默認(rèn)的語(yǔ)言Locale
  Locale get locale;
    
  // 當(dāng)前系統(tǒng)字體縮放比例霉颠。  
  double get textScaleFactor => _textScaleFactor;  
    
  // 當(dāng)繪制區(qū)域大小改變回調(diào)
  VoidCallback get onMetricsChanged => _onMetricsChanged;  
  // Locale發(fā)生變化回調(diào)
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  // 系統(tǒng)字體縮放變化回調(diào)
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  // 繪制前回調(diào)对碌,一般會(huì)受顯示器的垂直同步信號(hào)VSync驅(qū)動(dòng),當(dāng)屏幕刷新時(shí)就會(huì)被調(diào)用
  FrameCallback get onBeginFrame => _onBeginFrame;
  // 繪制回調(diào)  
  VoidCallback get onDrawFrame => _onDrawFrame;
  // 點(diǎn)擊或指針事件回調(diào)
  PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  // 調(diào)度Frame蒿偎,該方法執(zhí)行后朽们,onBeginFrame和onDrawFrame將緊接著會(huì)在合適時(shí)機(jī)被調(diào)用,
  // 此方法會(huì)直接調(diào)用Flutter engine的Window_scheduleFrame方法
  void scheduleFrame() native 'Window_scheduleFrame';
  // 更新應(yīng)用在GPU上的渲染,此方法會(huì)直接調(diào)用Flutter engine的Window_render方法
  void render(Scene scene) native 'Window_render';

  // 發(fā)送平臺(tái)消息
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) ;
  // 平臺(tái)通道消息處理回調(diào)  
  PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
  
  ... //其它屬性及回調(diào)
   
}

Window類(lèi)包含了當(dāng)前設(shè)備和系統(tǒng)的一些信息以及Flutter Engine的一些回調(diào)诉位。通過(guò)這些Binding 監(jiān)聽(tīng)Window對(duì)象的一些事件骑脱,然后將這些事件按照Framework的模型包裝,抽象再分發(fā)苍糠。

2叁丧、scheduleAttachRootWidget

WidgetsFlutterBinding初始化之后,接著會(huì)調(diào)用WidgetsBinding.attachRootWidget方法岳瞭,該方法負(fù)責(zé)將根Widget添加到RenderView上拥娄,

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

注意:

代碼中的renderView是一個(gè)RenderObject,它渲染樹(shù)的根

renderViewElement是renderView對(duì)應(yīng)的Element對(duì)象瞳筏,可見(jiàn)該方法主要完成根widget到根RenderObject再到跟Element的整個(gè)關(guān)聯(lián)過(guò)程稚瘾。

再來(lái)看看attachToRenderTree源碼實(shí)現(xiàn):

 /// Inflate this widget and actually set the resulting [RenderObject] as the
  /// child of [container].
  ///
  /// If `element` is null, this function will create a new element. Otherwise,
  /// the given element will have an update scheduled to switch to this widget.
  ///
  /// Used by [runApp] to bootstrap applications.
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        element!.assignOwner(owner);
      });
      owner.buildScope(element!, () {
        element!.mount(null, null);
      });
      // This is most likely the first time the framework is ready to produce
      // a frame. Ensure that we are asked for one.
      SchedulerBinding.instance!.ensureVisualUpdate();
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element!;
  }

該方法負(fù)責(zé)創(chuàng)建根element,即:RenderObjectToWidgetElement姚炕,并且將element于widget進(jìn)行關(guān)聯(lián)摊欠,即創(chuàng)建出widget數(shù)對(duì)對(duì)應(yīng)的element樹(shù)。如果element已經(jīng)創(chuàng)建過(guò)了钻心,則將根element中關(guān)聯(lián)的widget設(shè)為新的凄硼,由此可以看出element只會(huì)創(chuàng)建一次铅协,后面會(huì)進(jìn)行復(fù)用捷沸。那么BuildOwner是什么呢?其實(shí)它就是widget fragment的管理類(lèi)狐史,它跟蹤哪些widget需要重新構(gòu)建痒给。

3说墨、熱身幀繪制

    組件數(shù)在構(gòu)建(build)完成以后,回到``runApp``實(shí)現(xiàn)中苍柏,當(dāng)``attachRootWidget``后尼斧,最后一行調(diào)用WidgetsFlutterBinding實(shí)例的scheduleWarmUpFrame()方法,該方法在實(shí)例``SchedulerBinding``中试吁,它被調(diào)用后會(huì)立即進(jìn)行一次繪制棺棵,在此次繪制結(jié)束之前,該方法會(huì)鎖定事件分發(fā)熄捍,也就是說(shuō)在本次繪制結(jié)束完成之前Flutter將不會(huì)響應(yīng)各個(gè)事件烛恤,這可以保證在繪制過(guò)程中不會(huì)被再出發(fā)新的繪制。

scheduleWarmUpFrame()源碼

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

    _warmUpFrame = true;
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
    // We use timers here to ensure that microtasks flush in between.
    Timer.run(() {
      assert(_warmUpFrame);
      handleBeginFrame(null);
    });
    Timer.run(() {
      assert(_warmUpFrame);
      handleDrawFrame();
      // We call resetEpoch after this frame so that, in the hot reload case,
      // the very next frame pretends to have occurred immediately after this
      // warm-up frame. The warm-up frame's timestamp will typically be far in
      // the past (the time of the last real frame), so if we didn't reset the
      // epoch we would see a sudden jump from the old time in the warm-up frame
      // to the new time in the "real" frame. The biggest problem with this is
      // that implicit animations end up being triggered at the old time and
      // then skipping every frame and finishing in the new time.
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });

    // Lock events so touch events etc don't insert themselves until the
    // scheduled frame has finished.
    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }

這個(gè)函數(shù)其實(shí)就調(diào)用了兩個(gè)函數(shù)余耽,onBeginFrameonDrawFrame,最后渲染出來(lái)的首幀場(chǎng)景送入engine顯示到屏幕缚柏。這里使用 Timer.run()來(lái)異步運(yùn)行兩個(gè)回調(diào),就是為了在他們被調(diào)用之前有機(jī)會(huì)處理完微任務(wù)隊(duì)列(microtaskqueue)碟贾。

我們之前說(shuō)渲染流水線是由Vsync信號(hào)驅(qū)動(dòng)的币喧,但是上述過(guò)程都是在runApp()里完成的。并沒(méi)有看到什么地方告訴engine去調(diào)度一幀袱耽。這是因?yàn)槲覀兪窃谧鯢lutter的初始化杀餐。為了節(jié)省等待Vsync信號(hào)的時(shí)間,所以就直接把渲染流程跑完做出來(lái)第一幀圖像來(lái)了朱巨。

總結(jié)

Flutter 入口runApp分析完怜浅。我們了解到其實(shí)主要Flutter 框架的初始化過(guò)程核心作用主要是:

  1. 各種mixin 類(lèi) Binding的創(chuàng)建,建立與Flutter Engine的橋梁
  2. Element 蔬崩,Render根節(jié)點(diǎn)創(chuàng)立以及Element恶座,Render,Widget之間的關(guān)聯(lián)
  3. 初始幀的繪制
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沥阳,一起剝皮案震驚了整個(gè)濱河市跨琳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桐罕,老刑警劉巖脉让,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異功炮,居然都是意外死亡溅潜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)薪伏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)滚澜,“玉大人,你說(shuō)我怎么就攤上這事嫁怀∩杈瑁” “怎么了借浊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)萝招。 經(jīng)常有香客問(wèn)我蚂斤,道長(zhǎng),這世上最難降的妖魔是什么槐沼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任曙蒸,我火速辦了婚禮,結(jié)果婚禮上岗钩,老公的妹妹穿的比我還像新娘逸爵。我一直安慰自己,他們只是感情好凹嘲,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布师倔。 她就那樣靜靜地躺著,像睡著了一般周蹭。 火紅的嫁衣襯著肌膚如雪趋艘。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天凶朗,我揣著相機(jī)與錄音瓷胧,去河邊找鬼。 笑死棚愤,一個(gè)胖子當(dāng)著我的面吹牛搓萧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宛畦,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼瘸洛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了次和?” 一聲冷哼從身側(cè)響起反肋,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎踏施,沒(méi)想到半個(gè)月后石蔗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡畅形,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年养距,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片日熬。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棍厌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情定铜,我是刑警寧澤阳液,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布怕敬,位于F島的核電站揣炕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏东跪。R本人自食惡果不足惜畸陡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望虽填。 院中可真熱鬧丁恭,春花似錦、人聲如沸斋日。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)恶守。三九已至第献,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兔港,已是汗流浹背庸毫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衫樊,地道東北人飒赃。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像科侈,于是被迫代替她去往敵國(guó)和親载佳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容