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

簡介: Flutter從本質上來說仍是一個UI框架,它解決的是一套代碼在多端渲染的問題。在渲染管線的設計上更加精簡,加上自建渲染引擎,相比ReactNative、Weex以及WebView等方案,具備更好的性能體驗。本文將從架構和源碼的角度詳細分析Flutter渲染機制的設計與實現。較長,同窗們可收藏後再看。前端

image.png

寫在前面

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

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

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

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

Flutter因爲其自建渲染引擎,貼近原生的實現方式,得到了優秀的渲染性能。github

Flutter擁有本身的開發工具,開發語言、虛擬機,編譯機制,線程模型和渲染管線,和Android相比,它也能夠看作一個小型的OS了。web

第一次接觸Flutter,能夠看看Flutter的創始人Eric以前的訪談《What is Flutter?》,Eric以前致力於Chromium渲染管線的設計與開發,所以Flutter的渲染與Chromium有必定的類似之處,後面咱們會作下類比。算法

後面咱們會從架構和源碼的角度分析Flutter渲染機制的設計與實現,在此以前也能夠先看看Flutter官方對於渲染機制的分享《How Flutter renders Widgets》。視頻+圖文的方式會更加直觀,能夠有一個大致的理解。macos

架構分析

image.png

架構設計

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

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中,接着進行柵格化和合成上屏。

具體說來:
image.png

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的脈絡。

image.png
綠色:Widget 黃色:Element 紅色:RenderObject

以上即是Flutter渲染的總體流程,會有多個線程配合,多個模塊參與,拋開冗長的調用鏈,咱們針對每一步來具體分析。咱們在分析結構時把Flutter的渲染流程分爲了7大步,Flutter的timeline也能夠清晰地看到這些流程,以下所示:

image.png

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,而後再遞歸遍歷約束。單向數據流讓佈局流程變得更簡單,性能也更好。

image.png

對於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(繪製)的核心問題仍是解決繪製命令應該放到哪一個圖層的問題。

image.png

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一一對應。
image.png

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家的明星級項目,它們在渲染機制的設計上既有類似又有不一樣,藉着這個機會咱們對它們作個比較。

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

image.png

咱們再分別來看看Android、Chromium和Flutter是怎麼實現的。

Android渲染流水線:

image.png

Chromium渲染流水線:

image.png

Flutter渲染流水線:

image.png

相互比較:
image.png

寫在最後

最後的最後,談一談我對跨平臺生態的理解。

跨平臺容器生態至少能夠分爲三個方面:

image.png

前端框架生態

前端框架生態直接面向的是業務,它應該具有兩個特色:

  • 擁抱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技術團隊。

招聘本地生活-客戶端開發專家/高級技術專家-杭州/上海/北京,歡迎您的加盟!簡歷發送至 wushi@alibaba-inc.com

附錄

相關平臺
[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

相關文章
相關標籤/搜索