Fluttter 混合開發(fā)下 HybridComposition 和 VirtualDisplay 的實(shí)現(xiàn)與未來演進(jìn)

對(duì)于使用過 Flutter 的開發(fā)來說,應(yīng)該對(duì)在 Flutter 混合開發(fā)中,通過 PlatformView 接入原生控件的方式并不陌生,而如果你是從 Flutter 1.20 之前就開始使用 Flutter ,那么應(yīng)該對(duì)于 Android 上 PlatformView 的各種體驗(yàn)問題有過深刻的體會(huì),比如: WebView 里彈出鍵盤的問題。

??注意:文末有驚喜

從一個(gè)問題開始

恰巧最近一位朋友在 Flutter 2.10.1 上使用 webview_flutterflutter_pdfview 測(cè)試時(shí)出現(xiàn)了如下的問題:

attachToContext: GLConsumer is already attached to a context at android.graphics.SurfaceTexture.attachToGLContext(SurfaceTexture.java:289)

所以借著這個(gè)問題來給大家科普下 Flutter 里 PlatformView 實(shí)現(xiàn)的變遷和未來調(diào)整,首先這個(gè)問題的起因是因?yàn)椋?/p>

virtual displayes 和 hybrid composition 兩種 PlatformView實(shí)現(xiàn)混合使用。

因?yàn)閺?Flutter 2.10 開始,官方的 Plugin 如 webview_flutter 默都是使用 hybrid composition 的實(shí)現(xiàn),而第三方的 flutter_pdfview 目前還是使用以前的 virtual display ,這就出現(xiàn)了兩種 PlatformView 實(shí)現(xiàn)同時(shí)出現(xiàn)的情況。

當(dāng)然,官方在 2.10.2 版本的 #31390 上修復(fù)了這個(gè)問題, 問題的原因在于:當(dāng) rasterizer 任務(wù)運(yùn)行不同的線程時(shí),GrContext 會(huì)被重新創(chuàng)建,從而導(dǎo)致 texture 變成沒有初始化的狀態(tài),進(jìn)而重復(fù)調(diào)用 attachToGLContext 導(dǎo)致崩潰。

所以后續(xù)官方修復(fù)這個(gè)問題,就是在 attachToGLContext 之前,如果 texture 已經(jīng) attach 過,就先調(diào)用 detachFromGLContext 進(jìn)行釋放,從而避免了初始化 context 的問題。

但是從問題上看,其實(shí)這個(gè)問題并不是 2.10 才會(huì)出現(xiàn),而是只要在 SurfaceTextureWrapper 這個(gè)對(duì)象存在時(shí) ,混合使用 virtual displayeshybrid composition 就能引發(fā)這個(gè) bug 。

SurfaceTextureWrapper 是官方用于處理同步的問題,因?yàn)楫?dāng) SurfaceTexture 被釋放時(shí),由于 SurfaceTexture.release 是在 platform 線程被調(diào)用,而 attachToGLContext 是在 raster 線程被調(diào)用,不同線程調(diào)用時(shí)可能導(dǎo)致:當(dāng) attachToGLContext 被調(diào)用時(shí) texture 已經(jīng)被釋放了,所以需要 SurfaceTextureWrapper 用于實(shí)現(xiàn) Java 里同步鎖的效果。

所以如果在低版本不想升級(jí),那么可以選擇所有 Plugin 都使用 virtual display 模式或者 hybrid composition 模式,比如 webview_flutter 就提供了 WebView.platform 用于用戶自由選擇 PlatformView 的渲染模式。

當(dāng)然一般情況下我是更建議大家目前都使用 hybrid composition 模式,雖然兩種模式都有潛在問題,但是相比起來目前 virtual display 帶來的性能和鍵盤問題會(huì)讓人更難以接受。

區(qū)別和演進(jìn)

其實(shí)在之前的 《 Hybrid Composition 深度解析》 里就介紹過它們實(shí)現(xiàn)的區(qū)別,這里再結(jié)合上面的問題,從不一樣的角度介紹下它們的實(shí)現(xiàn)差異和變遷。

VirtualDisplay

一般 dart 代碼里直接使用 AndroidView 的我們就可以簡(jiǎn)單認(rèn)為是使用 virtual display ,比如 flutter_pdfview 1.2.2 版本 , 這種實(shí)現(xiàn)方式是 通過將 AndroidView 需要渲染的內(nèi)容繪制到 VirtualDisplays 實(shí)現(xiàn)中 ,然后在 VirtualDisplay 對(duì)應(yīng)的內(nèi)存里,繪制的畫面就可以通過其 Surface 獲取得到。

image

VirtualDisplay 類似于一個(gè)虛擬顯示區(qū)域,需要結(jié)合 DisplayManager 一起調(diào)用,一般在副屏顯示或者錄屏場(chǎng)景下會(huì)用到。VirtualDisplay 會(huì)將虛擬顯示區(qū)域的內(nèi)容渲染在一個(gè) Surface上。

如上圖所示,簡(jiǎn)單來說就是原生控件的內(nèi)容被繪制到內(nèi)存里,然后 Flutter Engine 通過相對(duì)應(yīng)的 textureId 就可以獲取到控件的渲染數(shù)據(jù)并顯示出來。

關(guān)于 virtual display 實(shí)現(xiàn),如果你需要對(duì)應(yīng)路徑去調(diào)試問題,可以參看如下流程:

image-20220305161230961

HybridComposition

使用 hybrid composition 相對(duì)會(huì)比直接使用 AndroidView 在代碼上更復(fù)雜一點(diǎn), 需要使用到 PlatformViewLink、 AndroidViewSurfacePlatformViewsService 這三個(gè)對(duì)象,首先我們要?jiǎng)?chuàng)建一個(gè) dart 控件:

  • 通過 PlatformViewLinkviewType 注冊(cè)了一個(gè)和原生層對(duì)應(yīng)的注冊(cè)名稱,這和之前的 PlatformView 注冊(cè)一樣;
  • 然后在 surfaceFactory 返回一個(gè) AndroidViewSurface 用于處理繪制和接收觸摸事件;
  • 最后在 onCreatePlatformView 方法使用 PlatformViewsService 初始化 AndroidViewSurface 和初始化所需要的參數(shù),同時(shí)通過 Engine 去觸發(fā)原生層的顯示。
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();
    },
  );
}

如果通過上面的問題來做個(gè)直觀的對(duì)比,就會(huì)是如下圖所示的變化:

image-20220305160606360

使用 hybrid composition 之后, PlatformView 是通過 FlutterMutatorView 把原生控件 addViewFlutterView 上,然后再通過 FlutterImageView 的能力去實(shí)現(xiàn)圖層的混合,簡(jiǎn)單解釋就是:

Flutter 只直接通過原生的 addView 方法將 PlatformView 添加到 FlutterView ,這就不需要什么 surface 渲染再去獲取的開銷,而當(dāng)你還需要再 PlatformView 上渲染 Flutter 自己的 Widget 時(shí),F(xiàn)lutter 就會(huì)通過再疊加一個(gè) FlutterImageView 來承載這個(gè) Widget 。

img

舉個(gè)例子,如下圖所示,其中:

  • 兩個(gè)灰色的 Re 是原生的 TextView;

  • 藍(lán)色、黃色、紅色的是 Flutter 的 Text

img

從渲染結(jié)果上可以看到:

  • 灰色的原生 TextView 通過 PlatformView 直接就通過原生的 addView 方法添加到 FlutterView 上;
  • 而紅色的 Flutter 的 Text 控件因?yàn)楹?PlatformView沒交集,所以還是 Flutter 原本的渲染邏輯;
  • 黃色和藍(lán)色的 Flutter 控件,因?yàn)楹? PlatformView 有交集,所以通過新的 FlutterImageView 做承載渲染。

使用 hybrid composition 后,在 Engine 去 SubmitFrame 時(shí),會(huì)通過 current_frame_view_count 去對(duì)每個(gè) view 畫面進(jìn)行規(guī)劃處理,然后會(huì)通過判定區(qū)域內(nèi)是否需要 CreateSurfaceIfNeeded 函數(shù),最終觸發(fā)原生的 createOverlaySurface 方法去創(chuàng)建 FlutterImageView。

    for (const SkRect& overlay_rect : overlay_layers.at(view_id)) {
      std::unique_ptr<SurfaceFrame> frame =
          CreateSurfaceIfNeeded(context,               //
                                view_id,               //
                                pictures.at(view_id),  //
                                overlay_rect           //
          );
      if (should_submit_current_frame) {
        frame->Submit();
      }
    }

如果有需要調(diào)試 hybrid composition 相關(guān)功能的,可以參考如下路徑, 和 virtual display 不同之處就是在 create 之后的路徑產(chǎn)生了變化 , 更多詳細(xì)演示可見:https://juejin.cn/post/6858473695939084295#heading-2

image-20220305165318255
image-20220305141848256

結(jié)論

所以可以看到,hybrid composition 保留了更多的原生控件效果,也節(jié)省了渲染成本 ,當(dāng)然目前 PlatformView 還有一個(gè)比較尖銳的問題,例如 #95343 的閃動(dòng)問題,這個(gè)問題看來在未來會(huì)通過更改渲染方式和紋理優(yōu)化來解決。

是的,還是因?yàn)樾阅艿葐栴},所以新的 PlatforView 實(shí)現(xiàn)來又要來了,從上面提到的 #31198 已經(jīng)合并可以猜測(cè),下一個(gè)穩(wěn)定版本中,現(xiàn)在的 virtual displayes 實(shí)現(xiàn)將不復(fù)存在,進(jìn)而替代的是通過新的 TextureLayer 實(shí)現(xiàn),未來不排除 hybrid composition 也會(huì)被取消,不知道大家此刻心情如何?

image-20220305170157117

簡(jiǎn)單說就是:

  • 新的 PlatformViewWrapper 會(huì)替換掉原本 virtual displaySurfaceTextureWrapper 相關(guān)的邏輯,通過對(duì)輸入的 Surface 進(jìn)行 lockHardwareCanvas 獲取到 Canvas ,再通過 super.draw(surfaceCanvas); 進(jìn)行繪制;
  • 關(guān)于 hybrid composition 目前看起里僅是更換了稱謂,只要核心邏輯沒有大變動(dòng);

而如果未來 PlatformViewWrapper 的實(shí)現(xiàn)效果良好 ,可以猜測(cè) hybrid composition 模式也會(huì)進(jìn)而退出歷史舞臺(tái),所以唯有感慨, Flutter 的技術(shù)演進(jìn)速度真的好快。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容