對(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_flutter 和 flutter_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 displayes 和 hybrid 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 獲取得到。

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)試問題,可以參看如下流程:

HybridComposition
使用 hybrid composition 相對(duì)會(huì)比直接使用 AndroidView 在代碼上更復(fù)雜一點(diǎn), 需要使用到 PlatformViewLink、 AndroidViewSurface 和 PlatformViewsService 這三個(gè)對(duì)象,首先我們要?jiǎng)?chuàng)建一個(gè) dart 控件:
- 通過
PlatformViewLink的viewType注冊(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ì)是如下圖所示的變化:

使用 hybrid composition 之后, PlatformView 是通過 FlutterMutatorView 把原生控件 addView 到 FlutterView 上,然后再通過 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 。

舉個(gè)例子,如下圖所示,其中:
兩個(gè)灰色的 Re 是原生的
TextView;藍(lán)色、黃色、紅色的是 Flutter 的
Text;

從渲染結(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


結(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ì)被取消,不知道大家此刻心情如何?

簡(jiǎn)單說就是:
- 新的
PlatformViewWrapper會(huì)替換掉原本 virtual display 里SurfaceTextureWrapper相關(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)速度真的好快。