Flutter 深入探索混合開發的技術演進

關于 Flutter 混合 PlatformView 的實現已經介紹過兩次,隨著 5 月份谷歌 IO 的接近,新的 PlatformView 實現應該也會隨之而來,本次就從頭到尾來一個詳細的關于 PlatformView 的演進總結。

Flutter 作為新一代的跨平臺框架,通過自定義渲染引擎的創新大大提高了跨平臺的性能和一致性,但也正是因為這點, 相比之下 Flutter 在混合開發時對于原生控件的支持成本更高。

Flutter 混合開發的難點

首先 Flutter 在混合開發中最大的難點就在于它獨立的渲染引擎,舉一個不是很恰當的例子:

Flutter 里混合開發類似與把原生控件渲染到 WebView 里。

大致上在 Flutter 里混合開發的感覺就是這樣,因為 Flutter UI 不會轉換為原生控件,而是由 Flutter Engine 使用 Skia 直接渲染在 Surface。

所以 Flutter 在最早出來時并不支持 WebViewMapView 這些常用的控件,這也導致了當時 Flutter 一度的風評不大好,所以衍生出了第一代非官方的混合開發支持,例如: flutter_webview_plugin。

在官方 WebView 控件支持出來之前 ,第三方是直接在 FlutterView 上覆蓋了一個新的原生控件,利用 Dart 中的占位控件來傳遞位置和大小。

Flutter 里幾乎所有渲染都是渲染到 FlutterView 這樣一個單頁面上,所以直接覆蓋一個新的原生 WebView 只能說緩解燃眉之急。

如下圖,在 Flutter 端 push 出來一個 設定好位置和大小SingleChildRenderObjectWidget ,從而得到需要顯示的大小和位置,將這些信息通過 MethodChannel 傳遞到原生層,在原生層 addContentView 一個指定大小和位置的 WebView

image.png

這樣看起來就像是在 Flutter 中添加了 WebView ,但實際這樣的操作只能說是“救急”,因為這樣的行為脫離了 Flutter 的渲染樹,其中一個問題就是:

當你跳轉 Flutter 其他頁面的時候會被當前原生的 WebView 擋?。徊⑶掖蜷_頁面的動畫時AppbarWebView 難以保持一致,因為 AppbarWebView 是出于兩個動畫體系和渲染體系。

就比如打開了新的 Flutter UI 2 頁面,但是由于它還是在 FlutterView 內,所以它會被 WebView 所遮擋。

image.png

但是這個“占位”顯示的思路,也起到了一定的作用,在后續 Flutter 支持原生 PlatformView 上起到了帶頭的作用。

Flutter 初步支持原生控件

為了讓 Flutter 真正走向大眾化,官方開始推出了官方基于 PlatformView 的系列實現,比如: webview_flutter ,而這個實現 “縫縫補補” 也被沿用至今,成了 Flutter 接入原生的方式 之一。

Android

PlatformView 的整個實現中 Android 坑一直是最多的,因為一開始 Android 上主要是通過 AndroidView 做完成這項工作,而它的 Virtual Displays 實現其實并不友好。

在 Flutter 中會將 AndroidView 需要渲染的內容繪制到 VirtualDisplays 中 ,然后在 VirtualDisplay 對應的內存中,繪制的畫面就可以通過其 Surface 獲取得到。

VirtualDisplay 類似于一個虛擬顯示區域,需要結合 DisplayManager 一起調用,一般在副屏顯示或者錄屏場景下會用到,在 VirtualDisplay 里會將虛擬顯示區域的內容渲染在一個 Surface 上。

image.png

如上圖所示,簡單來說就是原生控件的內容被繪制到內存里,然后 Flutter Engine 通過相對應的 textureId 就可以獲取到控件的渲染數據并顯示出來,這個過程 AndroidView 這個占位控件提供了 size、offset 等位置和大小參數。

通過從 VirtualDisplay 獲取紋理,并將其和 Flutter 原有的 UI 渲染樹混合,使得 Flutter 可以在自己的 Flutter Widget tree 中以圖形方式插入 Android 原生控件。

iOS

在 iOS 平臺上就不使用類似 VirtualDisplay 的方法,而是通過將 Flutter UI 分為兩個透明紋理來完成組合:一個在 iOS 平臺視圖之下,一個在其上面。

所以這樣的好處就是:

  • 需要在 “iOS平臺” 視圖下方呈現的Flutter UI,最終會被繪制到其下方的紋理上;
  • 而需要在 “平臺” 上方呈現的 Flutter UI,最終會被繪制在其上方的紋理;

iOS 上它們只需要在最后組合起來就可以了,通常這種方法更好,因為這意味著 Native View 可以直接添加到 Flutter 的 UI 層次結構中,但是可惜一開始 Android 平臺并不支持這種模式。

問題

盡管前面可以使用 VirtualDisplay 將 Android 控件嵌入到 Flutter UI 中 ,但這種 VirtualDisplay 這種介入還有其他麻煩的問題需要處理。

觸摸事件

默認情況下, PlatformViews 是沒辦法接收觸摸事件,因為 AndroidView 其實是被渲染在 VirtualDisplay 中 ,而每當用戶點擊看到的 "AndroidView" 時,其實他們就真正”點擊的是正在渲染的 Flutter 紋理 ,用戶產生的觸摸事件是直接發送到 Flutter View 中,而不是他們實際點擊的 AndroidView。

所以 AndroidView 使用 Flutter Framework 中檢測用戶的觸摸是否在需要的特殊處理的區域內:

當觸摸成功時會向 Android embedding 發送一條消息,其中包含 touch 事件的詳細信息。

這就變成有些本末倒置,觸摸事件從原生-Flutter-原生,中間的轉化導致某些信息被丟失,也導致了響應的延遲。

文字輸入

AndroidView 是無法獲取到文本輸入,因為 VirtualDisplay 所在的位置會始終被認為是 unfocused 的狀態。

所以需要做一套代理來處理 InputConnections 做輸入,甚至這個行為在 WebView 上更復雜,因為 WebView 具有自己內部的邏輯來創建和設置輸入連接,而這些輸入連接并沒有完全遵循 Android 的協議。

同步問題

另外還需要處理各種同步問題,比如官方就創建了一個 SurfaceTextureWrapper 用于處理同步的問題。

因為當承載 AndroidViewSurfaceTexture 被釋放時,由于 SurfaceTexture.release 是在 platform 線程被調用,而 attachToGLContext是在 raster 線程被調用,不同線程調用時可能導致:attachToGLContext被調用時 texture 已經被釋放了,所以需要 SurfaceTextureWrapper 用于實現 Java 里同步鎖的效果。

Flutter Hybrid Composition

所以經歷了 Virtual Display 的折磨之后,官方終于在后續推出了更為合理的實現。

實現邏輯

hybrid composition 的出現給 Flutter 提供了一種新的混合思路,那就是直接把原生控件添加到 Flutter 里一起組合渲染。

首先簡單介紹下使用,比起 Virtual Display 直接使用 AndroidView ,hybrid composition 相對會復雜一點點,dart 里使用到 PlatformViewLink 、AndroidViewSurface 、 PlatformViewsService 這三個對象。

正常在 dart 層面,使用 hybrid composition 接入原生控件:

  • 通過 PlatformViewLinkviewType 注冊了一個和原生層對應的注冊名稱,這和之前的 PlatformView 注冊一樣;
  • 然后在 surfaceFactory 返回一個 AndroidViewSurface 用于處理繪制和接收觸摸事件,同時也是一個類似占位的作用;
  • 最后在 onCreatePlatformView 方法使用 PlatformViewsService 初始化 AndroidViewSurface 和初始化所需要的參數,同時通過 Engine 去觸發原生層的顯示。
Widget build(BuildContext context) {
  // This is used in the platform side to register the view.
  final String viewType = 'hybrid-view-type';
  // Pass parameters to the platform side.
  final Map<String, dynamic> creationParams = <String, dynamic>{};

  return PlatformViewLink(
    viewType: viewType, 
    surfaceFactory:
        (BuildContext context, PlatformViewController controller) {
      return AndroidViewSurface(
        controller: controller,
        gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
        hitTestBehavior: PlatformViewHitTestBehavior.opaque,
      );
    },
    onCreatePlatformView: (PlatformViewCreationParams params) {
      return PlatformViewsService.initSurfaceAndroidView(
        id: params.id,
        viewType: viewType,
        layoutDirection: TextDirection.ltr,
        creationParams: creationParams,
        creationParamsCodec: StandardMessageCodec(),
      )
        ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
        ..create();
    },
  );
}

看起來好像是把一個 AndroidView 完成的事情變得相對復雜了,但是其實 hybrid composition 的實現相比其實更好理解。

使用 hybrid composition 之后, PlatformView 就直接通過 FlutterMutatorView(一個特殊的 FrameLayout) 把原生控件 addViewFlutterView上,然后再通過 FlutterImageView 的能力去實現多圖層的混合。

不理解嗎?沒事,我們后面會詳細介紹,先簡單解釋就是:

Flutter 只直接通過原生的 addView 方法將 PlatformView 添加到 FlutterView ,這就不需要什么 surface渲染再去獲取的開銷,而當你還需要再 PlatformView 上渲染 Flutter 自己的 Widget 時,Flutter 就會通過再疊加一個 FlutterImageView 來承載這個 Flutter Widget 。

image.png

深入例子詳解

接下來讓我們從實際例子去理解 Hybrid Composition ,結合 Andriod Studio 的 Layout Inspector,并開啟手機的繪制邊界來看會更直觀。

如下代碼所示,一般情況下我們運行之后會看到一片黑色,因為這時候 FlutterView 只有一個 FlutterSurfaceView 的子控件存在,此時雖然我們畫面上是有一個 Flutter 的紅色 RE 文本控件 ,不過因為是由 Flutter 直接在 Surface 直接繪制,所以 Layout Inspector 看不到只顯示黑色。

Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment.center,
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    )
  ],
)
image.png

此時我們添加一個通過 Hybrid Composition 實現一個原生的 TextView 控件,通過 PlatformView 在 Flutter 上渲染出一個灰色 RE 文本。

Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment(-0.6, -0.6),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment.center,
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    )
  ],
)
image.png

可以看到,如上圖所示,在我們的顯示布局邊界上可以清晰看到它的信息:

TextView 通過 FlutterMutatorView 被添加到 FlutterView 上被直接顯示出來。

所以 TextView 是直接在原生代碼上被 add 到 FlutterView 上,而不是提取紋理,另外可以看到,左側欄里多了一個 FlutterImageView ,并且之前看不到的 Flutter 控件紅色 RE 文本也出現了,背景也變成了 Flutter 上的白色。

我們先暫時忽略新出現的 FlutterImageView 和 Flutter UI 能夠出現在 Layout Inspector 的原因,留到后面再來分析,此時我們再新增加以一個紅色的 Flutter RE 控件到Stack 里,位于 PlatformView 的灰色 RE 下。

Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment(-0.4, -0.4),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    ),
    Align(
      alignment: Alignment(-0.6, -0.6),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment.center,
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    )
  ],
)
image.png

如上圖所示,可以看到布局和渲染效果正常,Flutter 的紅色 RE 被上面的 PlatformView 灰色 RE 遮擋了部分,這是符合代碼的渲染效果。

如果這時候我們把新增加的第二個紅色 RE 放到灰色 PlatformView 灰色 RE 上,會發生什么情況?

 Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment(-0.6, -0.6),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment(-0.4, -0.4),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    ),
    Align(
      alignment: Alignment.center,
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    )
  ],
)
image.png

可以看到紅色的 RE 成功被渲染到灰色 RE 上 ,而之所以能夠渲染上去的原因,是因為這個和 PlatformView 有交集的 Text ,被渲染到一個新增的 FlutterImageView 控件上, 也就是 Flutter 判斷了此時新紅色 RE 文本需要渲染到 PlatformView 上,所以添加了一個 FlutterImageView 用于承載這部分渲染內容。

如果這時候挪動第二個紅色的 RE 讓它和 PlatformView 沒有交集,但是還是在 Stack 里位于 PlatformView 之上會如何?

Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment(-0.6, -0.6),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment(-0.8, -0.8),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 50, color: Colors.red),
      ),
    ),
    Align(
      alignment: Alignment.center,
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    )
  ],
)
image.png

可以看到雖然 FlutterImageView 沒了,第二個紅色的 RE 也回到了默認的 Surface上,所以這就是 Hybrid Composition 混合原生控件的最基礎設計理念:

  • 直接把原生控件添加到 FlutterView 之上
  • 原生和 Flutter 控件混合堆疊時,用新的 FlutterImageView 來實現層級覆蓋;
  • 如果沒有交集就不需要新的 FlutterImageView;

關于 FlutterImageView 后面再展開,我們繼續這個例子,讓兩個 Flutter 的紅色 RE 都渲染到 PlatformView 的灰色的RE上會是什么情況?

Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment(0.6, 0),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment(0.6, 0),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 50, color: Colors.red),
      ),
    ),
    Align(
      alignment: Alignment.center,
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    )
  ],
)
image.png

如上圖所示,可以看到兩個紅色的 Flutter RE 控件共享了一個 FlutterImageView ,這里可以得到一個新的結論:**和 PlatformView 有交集的同層級 Flutter 控件會同享同一個 FlutterImageView 。 **

我們繼續調整示例,如下代碼我們新增多一個 PlatformView 的灰色 RE 控件,然后調整位置,但是 Flutter 控件都在一個層級上,運行之后可以看到,只要 Flutter 控件都在同一個層級,就同享同一個 FlutterImageView 。

Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment(-0.2, 0),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment(0.2, 0),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment(0, -0.1),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 50, color: Colors.red),
      ),
    ),
    Align(
      alignment: Alignment(0,  0.2),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    )
  ],
)
image.png

但是如果不在一個層級呢?我們調整兩個灰色 RE 的位置,讓 PlatformView 的灰色 RE 和 Flutter 的紅色 RE 交替出現。

Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment(-0.2, 0),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment(0, -0.1),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 50, color: Colors.red),
      ),
    ),
    Align(
      alignment: Alignment(0.2, 0),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment(0,  0.2),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 100, color: Colors.red),
      ),
    )
  ],
)
image.png

可以看到,兩個紅色的 Flutter RE 控件都單獨被渲染都一個 FlutterImageView 上,所以我們有新的結論:PlatformView 有交集的 Flutter 控件如果在不同層級,就需要不同的 FlutterImageView 來承載。

所以一般在使用 PlatformView 的場景上,不建議有過多的層級堆疊或者過于復雜的 UI 場景。

接著我們繼續測試,還記得前面說過 Virtual Display 上關于觸摸事件的問題,所以此時我們直接給 PlatformView 的 灰色 RE 在原生層添加點擊事件彈出 Toast 測試。

 Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment(-0.7, 0),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment(0.8, 0),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 50, color: Colors.red),
      ),
    ),
    Align(
      alignment: Alignment.center,
      child: Container(
        color: Colors.amber,
        child: new Text(
          "RE",
          style: TextStyle(fontSize: 100, color: Colors.red),
        ),
      ),
    ),
  ],
)
image.png

可以看到運行后點擊能夠正常彈出 Toast ,所以對于 PlatformView 來說本身的點擊和觸摸是可以正常保留,然后我們調整下紅色大 RE 和灰色 RE 讓他們產生交集,同時給紅色的大 RE 也添加點擊事件,彈出 SnackBar 。

Stack(
  fit: StackFit.expand,
  children: [
    Align(
      alignment: Alignment(-0.3, 0),
      child: SizedBox(
        height: 100,
        width: 100,
        child: NativeView(),
      ),
    ),
    Align(
      alignment: Alignment(0.8, 0),
      child: new Text(
        "RE",
        style: TextStyle(fontSize: 50, color: Colors.red),
      ),
    ),
    Align(
      alignment: Alignment.center,
      child: InkWell(
        onTap: () {
          ScaffoldMessenger.of(context)
              .showSnackBar(SnackBar(content: new Text("Re Click")));
        },
        child: Container(
          color: Colors.amber,
          child: new Text(
            "RE",
            style: TextStyle(fontSize: 100, color: Colors.red),
          ),
        ),
      ),
    ),
  ],
)
image.png

運行之后可以看到,點擊沒有被覆蓋的灰色部分,還是可以彈出 Toast ,點擊紅色 RE 和灰色 RE 的交集處,可以正常彈出 SnackBar

所以可以看到 Hybrid Composition 上這種實現,能更原汁原味地保流下原生控件的事件和特性,因為從原生 角度,它就是原生層面的物理堆疊。

現在大家應該大致對于 Hybrid Composition 有了一定理解,那回到前面那個一開始 Layout InSpector 黑屏 ,后來又能渲染出界面的原因,這就和首次添加 Hybrid Composition 時多出來的 FlutterImageView 有關系。

如下圖所示,可以看到此時原生的灰色 RE 和 Flutter 的紅色 RE 是沒有交集的,為什么會多出來一個 FlutterImageView 呢?

image.png

這就需要說到 flutterView.convertToImageView() 這個方法。

在 Flutter 渲染 Hybrid CompositionPlatformView 時,會有一個 flutterView.convertToImageView() 的操作,這個操作是:把默認的 FlutterSurfaceView 渲染切換到 FlutterImageView ,所以此時會有一個 新增的 FlutterImageView 出現在 FlutterSurfaceView 之上。

為什么需要 FlutterImageView ?那就要先理解下 FlutterImageView 是如何工作的,因為在前面我們說過,和 PlatformView 有交集的時候 Flutter 的控件也會被渲染到 FlutterImageView 上。

FlutterImageView 本身是一個原生的 Android View 控件,它的內部有幾個關鍵對象:

  • imageReader :提供一個 surface ,并且能夠直接訪問到 surface 里的圖像數據;
  • flutterRenderer : 外部傳入的 Flutter 渲染類,這里用于切換/提供 Flutter Engine 里的渲染所需 surface ;
  • currentImage : 從 imageReader 里提取出來的 Image 畫面;
  • currentBitmap :將 Image 轉為 Bitmap ,用于 onDraw 時繪制;

所以簡單地說 FlutterImageView 工作機制就是:通過 imageReader 提供 surface 給 Engine 渲染,然后把 imageReader 里的畫面提取出來,渲染到 FlutterImageViewonDraw。

所以回歸到前面的 flutterView.convertToImageView() ,在 Flutter 渲染 Hybrid CompositionPlatformView 時,會先把自己也變成了一個 FlutterImageView ,然后進入新的渲染流程:

  • Flutter 在 onEndFrame 時,也就是每幀結束時,會判斷當前界面是否還有 PlatformView,如果沒有就會切換會默認的 FlutterSurfaceView ;
  • 如果還存在 PlatformView ,就會調用 acquireLatestImage 去獲取當前 imageReader 里的畫面,得到新的 currentBitmap ,然后觸發 invalidate 。
  • invalidate 會導致 FlutterSurfaceView 執行 onDraw ,從而把 currentBitmap 里的內容繪制出來。
image.png

所以我們搞清楚了 FlutterImageView 的作用,也搞清楚了為什么有了 Hybrid CompositionPlatformView 之后,在 Android Studio 的 Layout Inspector 里可以看到 Flutter 控件的原因:

因為有 Hybrid Composition 之后, FlutterSurfaceView 變成了 FlutterImageView ,而 FlutterImageView 繪制是通過 onDraw ,所以可以在 Layout Inspector 里出現。

那為什么會有把 FlutterSurfaceView 變成了 FlutterImageView 這樣的操作?原因其實是為了更好的動畫同步和渲染效果。

因為前面說過,Hybrid Composition 是直接把添加到 FlutterView 上面,所以走的還是原生的渲染流程和時機,而這時候通過把 FlutterSurfaceView 變成了 FlutterImageView ,也就是把 Flutter 控件渲染也同步到原生的 OnDraw 上,這樣對于畫面同步會更好。

那有人就要說了,我就不喜歡 FlutterImageView 的實現,有沒有辦法不在使用 Hybrid Composition 時把 FlutterSurfaceView 變成了 FlutterImageView 呢?

有的,官方在 PlatformViewsService 內提供了對應的設置支持:

 PlatformViewsService.synchronizeToNativeViewHierarchy(false);

在設置為 false 之后,可以看到只有 Hybrid CompositionPlatformView 的內容才能在 Layout Inspector 上看到,而 FlutterSurfaceView 看起來就是黑色空白。

image.png
image.png

問題

Hybrid Composition 就是完美嗎? 肯定不是,事實上 Hybrid Composition 也有很多小問題,其中就比如性能問題。

例如在不使用 Hybrid Composition 的情況下,Flutter App 中 UI 是在特定的光柵線程運行,所以 Flutter 上 App 本身的主線程很少受到阻塞。

但是在 Hybrid Composition 下,Flutter UI 會由平臺的 onDraw 繪制,這可能會導致一定程度上需要消耗平臺性能和占用通信的開銷。

例如在 Android 10 之前, Hybrid Composition 需要將內存中的每個 Flutter 繪制的幀數據復制到主內存,之后再從 GPU 渲染復制回來 ,所以也會導致 Hybrid Composition 在 Android 10 之前的性能表現更差,例如在滾動列表里每個 Item 嵌套一個 Hybrid CompositionPlatformView 。

具體體現在 ImageReader 創建時,大于 29 的可以使用 HardwareBuffer ,而HardwareBuffer 允許在不同的應用程序進程之間共享緩沖區,通過 HardwareBuffers 可以映射各種硬件系統的可訪問 memory,例如 GPU。

image.png

所以如果當 Flutter 出現動畫卡頓時,或者你就應該考慮使用 Virtual Display 或者禁止 FlutterSurfaceView 變成了 FlutterImageView

事實上 Virtual Display 的性能也不好,因為它的每個像素都需要通過額外的中間圖形緩沖區。

未來變化

在目前 master 的 #31198 這個合并上,提出了新的實現方式用于替代現有的 Virtual Display 。

這個還未發布到正式本的調整上, Hybrid Composition 基本沒有變化,主要是調整了一些命名,主要邏輯還是在于 createForTextureLayer ,目前還無法保證它后續的進展,目前還有 一部分進度在 #97628 ,所以先簡單介紹下它的情況。

在這個新的實現上,Virtual Display 的邏輯變成了 PlatformViewWrapper , PlatformViewWrapper 本身是一個 FrameLayout ,同樣是 flutterView.addView(); ,基本邏輯和 Hybrid Composition 很像,只不過現在添加的是 PlatformViewWrapper

image.png

在這里 Virtual Display 沒有了,原本 Virtual Display 創建的 Surface 被設置到 PlatformViewWrapper 里面。

簡單介紹下:PlatformViewWrapper 里,會通過 surface.lockHardwareCanvas(); 獲取到當前 SurfaceCanvas ,并且通過 draw(surfaceCanvas) 傳遞給了 child

所以 child 的 UI 就被繪制到傳入的 Surface 上,而 Flutter Engine 根據 Surface 的 id 又可以獲取到對應的數據,通過一個不可視的 PlatformViewWrapper 完成了繪制切換而無需使用 VirtualDisplay 。

當然,目前在測試中接收到的反饋里有還不如以前的性能好,所以后續會如何調整還是需要看測試結果。

PS ,如果這個修改正式發布,可能 Flutter 的 Android miniSDK 版本就需要到 23 起步了。因為 lockHardwareCanvas() 需要 23 起,而不用兼容更低平臺的原因是 lockCanvas() 屬于 CPU copy ,性能上會慢很多

image.png
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容