天天看點

從架構到源碼:一文了解Flutter渲染機制

從架構到源碼:一文了解Flutter渲染機制

寫在前面

跨平台技術由于其一碼多端的生産力提升而表現出巨大的生命力,從早期的Hybrid App到ReactNative/Weex、小程式/快應用,再到現在的Flutter,跨平台技術一直在解決效率問題的基礎上最大化的解決性能和體驗問題。這也引出了任何跨平台技術都會面臨的核心問題:

  • 效率:解決在多應用、多平台、多容器上開發效率的問題,一碼多端,業務快跑。
  • 性能:解決的是業務的性能和體驗問題。

效率作為跨平台技術的基本功能,大家都能做到。問題是誰能把性能和體驗做得更好,在渲染技術這塊一共有三種方案:

  • WebView渲染:依賴WebView進行渲染,在功能和性能上有妥協,例如PhoneGap、Cordova、小程式(有的小程式底層也采用了ReactNative等渲染方案)等。
  • 原生渲染:上層擁抱W3C,通過中間層把前端架構翻譯為原生控件,例如ReactNative+React、Weex+Vue的組合,這種方案多了一層轉譯層,性能上有損耗。随着原生系統的更新,在相容性上也會有問題。
  • 自建渲染:自建渲染架構,底層使用Skia等圖形庫進行渲染,例如Flutter、Unity。

Flutter由于其自建渲染引擎,貼近原生的實作方式,獲得了優秀的渲染性能。

Flutter擁有自己的開發工具,開發語言、虛拟機,編譯機制,線程模型和渲染管線,和Android相比,它也可以看做一個小型的OS了。

第一次接觸Flutter,可以看看Flutter的創始人Eric之前的訪談《What is Flutter?》,Eric之前緻力于Chromium渲染管線的設計與開發,是以Flutter的渲染與Chromium有一定的相似之處,後面我們會做下類比。

後面我們會從架構和源碼的角度分析Flutter渲染機制的設計與實作,在此之前也可以先看看Flutter官方對于渲染機制的分享《How Flutter renders Widgets》。視訊+圖文的方式會更加直覺,可以有一個大體的了解。

架構分析

從架構到源碼:一文了解Flutter渲染機制

架構設計

從結構上看,Flutter渲染由UI Thread與GPU Thread互相配合完成。

1)UI Thread

對應圖中1-5,執行Dart VM中的Dart代碼(包含應用程式和Flutter架構代碼),主要負責Widget Tree、Element Tree、RenderObject Tree的建構,布局、以及繪制生成繪制指令,生成Layer Tree(儲存繪制指令)等工作。

2)GPU Thread

對應圖中6-7,執行Flutter引擎中圖形相關代碼(Skia),這個線程通過與GPU通信,擷取Layer Tree并執行栅格化以及合成上屏等操作,将Layer Tree顯示在螢幕上。

注:圖層樹(Layer Tree)是Flutter組織繪制指令的方式,類似于Android Rendering裡的View DisplayList,都是組織繪制指令的一種方式。           

UI Thread與GPU Thread屬于生産者和消費者的角色。

流程設計

我們知道Android上的渲染都是在VSync信号驅動下進行的,Flutter在Android上的渲染也不例外,它會向Android系統注冊并等待VSync信号,等到VSync信号到來以後,調用沿着C++ Engine->Java Engine,到達Dart Framework,開始執行Dart代碼,經曆Layout、Paint等過程,生成一棵Layer Tree,将繪制指令儲存在Layer中,接着進行栅格化和合成上屏。

具體說來:

從架構到源碼:一文了解Flutter渲染機制

1)向Android系統注冊并等待VSync信号

Flutter引擎啟動時,會向Android系統的Choreographer(管理VSync信号的類)注冊并接收VSync信号的回調。

2)接收到VSync信号,通過C++ Engine向Dart Framework發起渲染調用

當VSync信号産生以後,Flutter注冊的回調被調用,VsyncWaiter::fireCallback() 方法被調用,接着會執行 Animator::BeiginFrame(),最終調用到 Window::BeginFrame() 方法,WIndow執行個體是連接配接底層Engine和Dart Framework的重要橋梁,基本上與平台相關的操作都會通過Window執行個體來連接配接,例如input事件、渲染、無障礙等。

3)Dart Framework開始在UI線程執行渲染邏輯,生成Layer Tree,并将栅格化任務post到GPU線程執行

Window::BeiginFrame() 接着調用,執行到 RenderBinding::drawFrame() 方法,這個方法會去驅動UI界面上的dirty節點(需要重繪的節點)進行重新布局和繪制,如果渲染過程中遇到圖檔,會先放到Worker Thead去加載和解碼,然後再放到IO Thread生成圖檔紋理,由于IO Thread和GPI Thread共享EGL Context,是以IO Thread生成的圖檔紋理可以被GPU Thread直接通路。

4)GPU線程接收到Layer Tree,進行栅格化以及合成上屏的工作

Dart Framework繪制完成以後會生成繪制指令儲存在Layer Tree中,通過 Animator::RenderFrame() 把Layer Tree送出給GPU Thread,GPU Thread接着執行栅格化和上屏顯示。之後通過 Animator::RequestFrame() 請求接收系統的下一次VSync信号,如此循環往複,驅動UI界面不斷更新。

逐個調用流程比較長,但是核心點沒多少,不用糾結調用鍊,抓住關鍵實作即可,我們把裡面涉及到的一些主要類用顔色分了個類,對着這個類圖,基本可以摸清Flutter的脈絡。

從架構到源碼:一文了解Flutter渲染機制

綠色:Widget 黃色:Element 紅色:RenderObject

以上便是Flutter渲染的整體流程,會有多個線程配合,多個子產品參與,抛開冗長的調用鍊,我們針對每一步來具體分析。我們在分析結構時把Flutter的渲染流程分為了7大步,Flutter的timeline也可以清晰地看到這些流程,如下所示:

從架構到源碼:一文了解Flutter渲染機制

UI Thread

1)Animate

由 handleBeiginFrame() 方法的transientCallbacks觸發,如果沒有動畫,則該callback為空;如果有動畫,則會回調 Ticker.tick() 觸發動畫Widget更新下一幀的值。

2)Build

由 BuildOwner.buildScope() 觸發,主要用來建構或者更新三棵樹,Widget Tree、Element Tree和RenderObject Tree。

3)Layout

由 PipelineOwner.flushLayout() 觸發,它會調用 RenderView.performLayout(),周遊整棵Render Tree,調用每個節點的 layout(),根據build過程記錄的資訊,更新dirty區域RenderObject的排版資料,使得每個RenderObject最終都能有正确的大小(size)和位置(position,儲存在parentData中)。

4)Compositing Bits

由 PipelineOwner.flushCompositingBits() 觸發,更新具有dirty合成位置的渲染對象,此階段每個渲染對象都會了解其子項是否需要合成,在繪制階段使用此資訊選擇如何實作裁剪等視覺效果。

5)Paint

由 PipeOwner.flushPaint() 觸發,它會調用 RenderView.paint()。最終觸發各個節點的 paint(),最終生成一棵Layer Tree,并把繪制指令儲存在Layer中。

6)Submit(Compositing)

由 renderView.compositeFrame() 方法觸發,這個地方官方的說法叫Compositing,不過我覺得叫Compositing有歧義,因為它并不是在合成,而是把Layer Tree送出給GPU Thread,因而我覺得叫Submit更合适。

GPU Thread

7)Compositing

由 Render.compositeFrame() 觸發,它通過Layer Tree建構一個Scene,傳給Window進行最終的光栅化。

GPU Thread通過Skia向GPU繪制一幀資料,GPU将幀資訊儲存在FrameBuffer裡,然後根據VSync信号周期性的從FrameBuffer取出幀資料交給顯示器,進而顯示出最終的界面。

Rendering Pipeline

Flutter引擎啟動時,向Android系統的Choreographer注冊并接收VSync信号,GPU硬體産生VSync信号以後,系統便會觸發回調,并驅動UI線程進行渲染工作。

1 Animate

觸發方法:由 handleBeiginFrame() 方法的transientCallbacks觸發

Animate在 handleBeiginFrame() 方法裡由transientCallbacks觸發,如果沒有動畫,則該callback為空;如果有動畫,則會回調 Ticker._tick() 觸發動畫Widget更新下一幀的值。

void handleBeginFrame(Duration rawTimeStamp) {
    ...
    try {
      // TRANSIENT FRAME CALLBACKS
      Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
        if (!_removedIds.contains(id))
          _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
      });
      ...
    } finally {
      ...
    }
  }           

handleBeiginFrame() 處理完成以後,接着調用 handleDrawFrame(),handleDrawFrame() 會觸發以下回調:

  • postFrameCallbacks用來通知監聽者繪制已經完成。
  • pesistentCallbacks用來觸發渲染。

這兩個回調都是SchedulerBinding内部的回調隊列,如下所示:

  • _transientCallbacks:用于存放一些臨時回調,目前是在 Ticker.scheduleTick() 中注冊,用來驅動動畫。
  • _persistentCallbacks:用來存放一些持久回調,不能在此回調中再請求新的繪制幀,持久回調一經注冊就不嫩嫩移除, RenderBinding.initInstaces().addPersitentFrameCallback() 添加了一個持久回調,用來觸發 drawFrame()。
  • _postFrameCallbacks:在Frame結束時會被調用一次,調用後會被移除,它主要是用來通知監聽者這個Frame已經完成。

接着會調用 WidgetBinder.drawFrame() 方法,它會先調用會先調用 BuildOwner.buildScope() 觸發樹的更新,然後才進行繪制。

@override
void drawFrame() {
  ...
  try {
    if (renderViewElement != null)
      buildOwner.buildScope(renderViewElement);
    super.drawFrame();
    buildOwner.finalizeTree();
  } finally {
    assert(() {
      debugBuildingDirtyElements = false;
      return true;
    }());
  }
  ...
}           

接着調用 RenderingBinding.drawFrame() 觸發layout、paingt等流程。

void drawFrame() {
  assert(renderView != null);
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}           

以上便是核心流程代碼,我們接着來Build的實作。

2 Build

觸發方法:由 BuildOwner.buildScope() 觸發。

我們上面說到,handleDrawFrame() 會觸發樹的更新,事實上 BuildOwner.buildScope() 會有兩種調用時機:

  • 樹建構(應用啟動時):我們上面提到的 runApp() 方法調用的 scheduleAttachRootWidget() 方法,它會建構Widgets Tree、Element Tree與RenderObject Tree三棵樹。
  • 樹更新(幀繪制與更新時):這裡不會重新建構三棵樹,而是隻會更新dirty區域的Element。

也即是說樹的建構和更新都是由 BuildOwner.buildScope() 方法來完成的。它們的差别在于樹建構的時候傳入了一個 element.mount(null, null) 回調。在 buildScope() 過程中會觸發這個回調。

這個回調會建構三棵樹,為什麼會有三棵樹呢,因為Widget隻是對UI元素的一個抽象描述,我們需要先将其inflate成Element,然後生成對應的RenderObject來驅動渲染,如下所示:

  • Widget Tree:為Element描述需要的配置,調用createElement方法建立Element,決定Element是否需要更新。Flutter通過查分算法比對Widget樹前後的變化,來決定Element的State是否改變。
  • Element Tree:表示Widget Tree特定位置的一個執行個體,調用createRenderObject建立RenderObject,同時持有Widget和RenderObject,負責管理Widget的配置和RenderObjec的渲染。Element的狀态由Flutter維護,開發人員隻需要維護Widget即可。
  • RenderObject Tree:RenderObject繪制,測量和繪制節點,布局子節點,處理輸入事件。

3 Layout

觸發方法:由 PipelineOwner.flushLayout() 觸發。

  • 相關文檔:Understanding constraints
  • 相關源碼:PipelineOwner.flushLayout()

Layout是基于單向資料流來實作的,父節點向子節點傳遞限制(Constraints),子節點向父節點傳遞大小(Size,儲存在父節點的parentData變量中)。先深度周遊RenderObject Tree,然後再遞歸周遊限制。單向資料流讓布局流程變得更簡單,性能也更好。

從架構到源碼:一文了解Flutter渲染機制

對于RenderObject而言,它隻是提供了一套基礎的布局協定,沒有定義子節點模型、坐标系統和具體的布局協定。它的子類RenderBox則提供了一套笛卡爾坐标體系(和Android&iOS一樣),大部分RenderObject類都是直接繼承RenderBox來實作的。RenderBox有幾個不同的子類實作,它們各自對應了不同的布局算法。

  • RenderFlex:彈性布局,這是一種很常見的布局方式,它對應的是Widget元件Flex、Row和Column。關于這一塊的布局算法代碼注釋裡有描述,也可以直接看這篇文章的解釋。
  • RenderStack:棧布局。

我們再來聊聊Layout流程中涉及的兩個概念邊界限制(Constraints)和重新布局邊界(RelayoutBoundary)。

邊界限制(Constraints):邊界限制是父節點用來限制子節點的大小的一種方式,例如BoxConstraints、SliverConstraints等。

RenderBox提供一套BoxConstraints,如圖所示,它會提供以下限制:

  • minWidth
  • maxWidth
  • minHeight
  • maxHeight

利用這種簡單的盒模型限制,我們可以非常靈活的實作很多常見的布局,例如完全和父節點一樣的大小,垂直布局(寬度和父節點一樣大)、水準布局(高度和父容器一樣大)。

通過Constraints和子節點自己配置的大小資訊,就可以最終算出子節點的大小,接下來就需要計算子節點的位置。子節點的位置是由父節點來決定的。

重新布局邊界(RelayoutBoundary):為一個子節點設定重新布局邊界,這樣當它的大小發生變化時,不會導緻父節點重新布局,這是個标志位,在标記dirty的markNeedsLayout()方法中會檢查這個标記位來決定是否重新進行布局。

重新布局邊界這種機制提升了布局排版的性能。

通過Layout,我們了解了所有節點的位置和大小,接下來就會去繪制它們。

4 Compositing Bits

觸發方法:由 PipelineOwner.flushCompositingBits() 觸發。

在Layout之後,在Paint之前會先執行Compositing Bits,它會檢查RenderObject是否需要重繪,然後更新RenderObject Tree各個節點的needCompositing标志。如果為true,則需要重繪。

5 Paint

觸發方法:由 PipeOwner.flushPaint() 觸發。

相關源碼:

  • Dart層調用入口:painting.dart
  • C++層實作:canvas.cc

我們知道現代的UI系統都會進行界面的圖層劃分,這樣可以進行圖層複用,減少繪制量,提升繪制性能,是以Paint(繪制)的核心問題還是解決繪制指令應該放到哪個圖層的問題。

從架構到源碼:一文了解Flutter渲染機制

Paint的過程也是單向資料流,先向下深度周遊RenderObject Tree,再遞歸周遊子節點,周遊的過程中會決定每個子節點的繪制指令應該放在那一層,最終生成Layer Tree。

和Layout一樣,為了提到繪制性能,繪制階段也引入了重新繪制邊界。

重新繪制邊界(RepaintBoundary):為一個子節點設定重新繪制邊界,這樣當它需要重新繪制時,不會導緻父節點重新繪制,這是個标志位,在标記dirty的markNeedsPaint()方法中會檢查這個标記位來決定是否重新進行重繪。

事實上這種重繪邊界的機制相對于把圖層分層這個功能開放給了開發者,開發者可以自己決定自己的頁面那一塊在重繪時不參與重繪(例如滾動容器),以提升整體頁面的性能。重新繪制邊界會改變最終的圖層樹(Layer Tree)結構。

當然這些重繪邊界并不都需要我們手動放置,大部分Widget元件會自動放置重繪邊界(自動分層)。

設定了RepaintBoundary的就會額外生成一個圖層,其所有的子節點都會被繪制在這個新的圖層上,Flutter中使用圖層來描述一個層次上(一個繪制指令緩沖區)的所有RenderObject,根節點的RenderView會建立Root Layer,并且包含若幹個子Layer,每個Layer又包含多個RenderObject,這些Layer便形成了一個Layer Tree。每個RenderObject在繪制時,會産生相關的繪制指令和繪制參數,并儲存在對應的Layer上。

相關Layer都繼承Layer類,如下所示:

  • ClipRectLayer:矩形裁剪層,可以指定裁剪和矩形行為參數。共有4種裁剪行為,none、hardEdge、antiAlias、antiAliashWithSaveLayer。
  • ClipRRectLayer:圓角矩形裁剪層,行為同上。
  • ClipPathLayer:路徑裁剪層,可以指定路徑和行為裁剪參數,行為同上。
  • OpacityLayer:透明層,可以指定透明度和偏移(畫布坐标系原點到調用者坐标系原點的偏移)參數。
  • ShaderMaskLayer:着色層,可以指定着色器矩陣和混合模式參數。
  • ColorFilterLayer:顔色過濾層,可以指定顔色和混合模式參數。
  • TransformLayer:變換圖層,可以指定變換矩陣參數。
  • BackdropFilterLayer:背景過濾層,可以指定背景圖參數。
  • PhysicalShapeLayer:實體性狀層,可以指定顔色等八個參數。

具體可以參考文章上方的Flutter類圖。

聊完了繪制的基本概念,我們再來看看繪制的具體流程,上面提到渲染第一幀的時候,會從根節點RenderView開始,逐個周遊所有子節點進行操作。如下所示:

1)建立Canvas對象

Canvas對象通過PaintCotext擷取,它内部會建立一個PictureLayer,并通過ui.PictureRecorder調用到C++層建立一個Skia的SkPictureRecorder的執行個體,并通過SkPictureRecorder建立SkCanvas,而後将SkCanvas傳回給Dart Framework使用。SkPictureRecorder可以用來記錄生成的繪制指令。

2)通過Canvas執行繪制

繪制指令會被SkPictureRecorder記錄下來。

3)通過Canvas結束繪制,準備進行栅格化

繪制結束後,會調用 Canvas.stopRecordingIfNeeded() 方法,它會接着去調用C++層的SkPictureRecorder::endRecording()方法生成一個Picture對象并儲存在PictureLayer中,Picture對象包含了所有的繪制指令。所有的Layer繪制完成,形成Layer Tree。

繪制完成以後,接着就可以向GPU Thread送出Layer Tree了。

6 Submit(Compositing)

觸發方法:由 renderView.compositeFrame() 方法觸發。

  • Dart層調用入口:compositing.dart widow.dart
  • C++層實作:scene.cc scene_builder.cc
注:這個地方官方的說法叫Compositing,不過我覺得叫Compositing有歧義,因為它并不是在合成,而是把Layer Tree送出給GPU Thread,因而我覺得叫Submit更合适。
void compositeFrame() {
    Timeline.startSync('Compositing', arguments: timelineArgumentsIndicatingLandmarkEvent);
    try {
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      _window.render(scene);
      scene.dispose();
      assert(() {
        if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
          debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
        return true;
      }());
    } finally {
      Timeline.finishSync();
    }
  }           
  • 建立SceneBuilder對象,并通過 SceneBuilder.addPicture() 将上文中生成的Picture添加到SceneBuilder對象對象中。
  • 通過 SceneBuilder.build() 方法生成Scene對象,接着會通過window.render(scene)将包含繪制指令的Layer Tree送出給CPU線程進行光栅化和合成。

在這個過程中Dart Framework層的Layer會被轉換為C++層使用的flow::layer,Flow子產品是一個基于Skia的簡單合成器,運作在GPU線程,并向Skia上傳指令資訊。Flutter Engine使用flow緩存Paint階段生成的繪制指令和像素資訊。我們在Paint階段的Layer,它們都與Flow子產品裡的Layer一一對應。

從架構到源碼:一文了解Flutter渲染機制

Graphics Pipeline

7 Raster&Compositing

有了包含渲染指令的Layer Tree以後就可以進行光栅化和合成了。

光栅化是把繪制指令轉換成對應的像素資料,合成是把各圖層栅格化後的資料進行相關的疊加和特性處理。這個流程稱為Graphics Pipeline。

相關代碼:rasterizer.cc

Flutter采用的是同步光栅化。什麼是同步光栅化?

同步光栅化:

光栅化和合成在一個線程,或者通過線程同步等方式來保證光栅化和合成的的順序。

直接光栅化:直接執行可見圖層的DisplayList中可見區域的繪制指令進行光栅化,在目标Surface的像素緩沖區上生成像素的顔色值。

間接光栅化:為指定圖層配置設定額外的像素緩沖區(例如Android提供View.setLayerType允許應用為指定View提供像素緩沖區,Flutter提供了Relayout Boundary機制來為特定圖層配置設定額外緩沖區),該圖層光栅化的過程中會先寫入自身的像素緩沖區,渲染引擎再将這些圖層的像素緩沖區通過合成輸出到目标Surface的像素緩沖區。

異步分塊光栅化:

圖層會按照一定的規則粉塵同樣大小的圖塊,光栅化以圖塊為機關進行,每個光栅化任務執行圖塊區域内的指令,将執行結果寫入分塊的像素緩沖區,光栅化和合成不在一個線程内執行,并且不是同步的。如果合成過程中,某個分塊沒有完成光栅化,那麼它會保留白白或者繪制一個棋盤格圖形。

Android和Flutter采用同步光栅化政策,以直接光栅化為主,光栅化和合成同步執行,在合成的過程中完成光栅化。而Chromium采用異步分塊光栅化測量,圖層會進行分塊,光栅化和合成異步執行。

從文章上方的序列圖可以看到,光栅化的入口是 Rasterizer::DoDraw() 方法。它會接着調用 ScopedFrame::Raster() 方法,這個方法就是光栅化的核心實作,它主要完成以下工作:

  • LayerTree::Preroll():處理繪制前的一些準備工作。
  • LayerTree::Paint():嵌套調用不通Layer的繪制方法。
  • SkCanvas::Flush():将資料flush給GPU。
  • AndroidContextGL::SwapBuffers():交換幀緩存給顯示器顯示。

到這裡我們Flutter整體的渲染實作我們就分析完了。

Android、Chromium與Flutter

Android、Chromium、Flutter都作為Google家的明星級項目,它們在渲染機制的設計上既有相似又有不同,借着這個機會我們對它們做個比較。

現代渲染流水線的基本設計:

從架構到源碼:一文了解Flutter渲染機制

我們再分别來看看Android、Chromium和Flutter是怎麼實作的。

Android渲染流水線:

從架構到源碼:一文了解Flutter渲染機制

Chromium渲染流水線:

從架構到源碼:一文了解Flutter渲染機制

Flutter渲染流水線:

從架構到源碼:一文了解Flutter渲染機制

互相比較:

從架構到源碼:一文了解Flutter渲染機制

寫在最後

最後的最後,談一談我對跨平台生态的了解。

跨平台容器生态至少可以分為三個方面:

從架構到源碼:一文了解Flutter渲染機制

前端架構生态

前端架構生态直接面向的是業務,它應該具備兩個特點:

  • 擁抱W3C生态
  • 相對穩定性

它應該是擁抱W3C生态的。W3C生态是一個繁榮且充滿活力的生态,它會發展的更久更遠。試圖抛棄W3C生态,自建子集的做法很難走的長遠。這從微信小程式、Flutter都推出for web系列就能看出端倪。

它應該是相對穩定的。不能說我們每換一套容器,前端的業務就需要重新寫一遍,例如我們之前做H5容器,後來做小程式容器,因為DSL不通,前端要花大力氣将業務重寫。雖然小程式是一碼多端,但是我認為這并沒有解決效率問題,主要存在兩個問題:

  • 前端的學習成本增加,小程式的DSL還算簡單,Flutter的Widget體系學習起來就需要花上一點時間,這些對于團隊來說都是成本。
  • 業務代碼重寫,大量邏輯需要梳理,而且老業務并不一定都适合遷移到新容器上,比如小程式本來就是個很輕量的解決方案,但是我們在上面堆積了很多功能,造成了嚴重的體驗問題。

在這種情況下,業務很難實作快速奔跑。是以說不管底層容器怎麼變,前端的架構一定是相對穩定的。而這種穩定性就有賴于容器統一層。

容器統一層

容器統一層是在前端架構和容器層之間的一個層級。它定義了容器提供的基本能力,這些能力就像協定一樣,是相對穩定的。

協定是非常重要的,就像OpenGL協定一樣,有了OpenGL協定,不管底層的渲染方案如何實作,上層的調用是不用變的。對于我們的業務也是一樣,圍繞着容器統一層,我們需要沉澱通用的解決方案。

  • 統一API解決方案
  • 統一性能解決方案
  • 統一元件解決方案
  • 統一配套設施解決方案
  • 等等

這些東西不能說每搞一套容器,我們都要大刀闊斧重來一遍,這種做法是有問題的。已經做過的東西,遇到新的技術就推倒重來,隻能說明以前定義的方案考慮不周全,沒有考慮沉澱統一和擴充的情況。

如果我們自顧自的一遍遍做着功能重複的技術方案,業務能等着我們嗎。

容器層

容器層的疊代核心是為了在解決效率問題的基礎上最大化的解決性能和體驗問題。

早期的ReactNative模式解決了效率了問題,但是多了一個通信層(ReactNative是依靠将虛拟DOM的資訊傳遞給原生,然後原生根據這些布局資訊建構對應的原生控件樹來實作的原生渲染)存在性能問題,而且這種轉譯的方式需要适配系統版本,帶來更多的相容性問題。

微信後續又推出了小程式方案,在我看來,小程式方案不像是一個技術方案,它更像是一個商業解決方案,解決了平台大流量規範管理和分發的問題,給業務方提供通用的技術解決方案,當然小程式底層的渲染方案也是多種多樣的。

後起之秀Flutter解決的痛點是性能能力,它自建了一套GUI系統,底層直接調用Skia圖形庫進行渲染(與Android的機制一樣),進而實作了原生渲染。但是它基于開發效率、性能以及自身生态等因素的考慮最終選擇了Dart,這種做法無疑是直接抛棄了繁榮的前端生态,就跨平台容器的發展曆史來看,在解決效率與性能的基礎上,最大化的擁抱W3C生态,可能是未來最好的方向。Flutter目前也推出了Flutter for Web,從它的思路來看,是先打通Android與iOS,再逐漸向Web滲透,我們期待它的表現。

容器技術是動态向前發展的,我們今年搞Flutter,明年可能還會搞其他技術方案。在方案變遷的過程中,我們需要保證業務快速平滑的過度,而不是每次大刀闊斧的再來一遍。

随着手機性能的提升,WebView的性能也越來越好,Flutter又為解決性能問題提供了新的思路,一個基礎設施完善,體驗至上,一碼多端的跨平台容器生态值得期待。

最後的最後

歡迎加入本地生活終端技術部!

本地生活終端技術部隸屬于阿裡本地生活使用者技術部,從事用戶端技術研發工作,主要負責本地生活餓了麼App 和 口碑App 的用戶端架構、基礎中間件、跨平台技術解決方案,以及賬号、首頁、全局購物車、收銀台、訂單清單、紅包卡券、直播、短視訊等平台化核心業務鍊路。目前團隊規模50+人,我們依托阿裡強大的終端技術底盤,以及本地生活的業務土壤,緻力于打造最優秀的O2O技術團隊。

招聘本地生活-用戶端開發專家/進階技術專家-杭州/上海/北京,歡迎您的加盟!履歷發送至 [email protected]

附錄

相關平台

[1]Flutter pub.dev

https://pub.dev/flutter/packages

相關文檔

[1]Flutter 官方文檔

https://flutter.dev/docs/get-started/install/macos

[2]Flutter for Android developers

https://flutter.dev/docs/get-started/flutter-for/android-devs

[3]Flutter Widget Doc

https://flutter.dev/docs/reference/widgets

[4]Flutter API Doc(

https://api.flutter.dev/

[5]Dart Doc

https://dart.dev/guides/language

相關源碼

[1]Dart Framework

https://github.com/flutter/flutter/tree/master/packages

[2]Flutter Engine

https://github.com/flutter/engine

相關資源

[1]Flutter Render Pipeline

https://www.youtube.com/watch?v=UUfXWzp0-DU

[2]How Flutter renders Widgets

https://www.youtube.com/watch?v=996ZgFRENMs

[3]深入了解Flutter的高性能圖形渲染 video

https://www.bilibili.com/video/av48772383

[4]深入了解Flutter的高性能圖形渲染 ppt

https://files.flutter-io.cn/events/gdd2018/Deep_Dive_into_Flutter_Graphics_Performance.pdf