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

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

??注意:文末有驚喜

從一個問題開始

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

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

所以借著這個問題來給大家科普下 Flutter 里 PlatformView 實現(xiàn)的變遷和未來調(diào)整,首先這個問題的起因是因為:

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

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

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

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

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

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

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

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

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

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

VirtualDisplay

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

image

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

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

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

image-20220305161230961

HybridComposition

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

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

如果通過上面的問題來做個直觀的對比,就會是如下圖所示的變化:

image-20220305160606360

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

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

img

舉個例子,如下圖所示,其中:

  • 兩個灰色的 Re 是原生的 TextView;

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

img

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

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

使用 hybrid composition 后,在 Engine 去 SubmitFrame 時,會通過 current_frame_view_count 去對每個 view 畫面進(jìn)行規(guī)劃處理,然后會通過判定區(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 還有一個比較尖銳的問題,例如 #95343 的閃動問題,這個問題看來在未來會通過更改渲染方式和紋理優(yōu)化來解決。

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

image-20220305170157117

簡單說就是:

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

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

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

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