關于 Flutter 混合 PlatformView
的實現已經介紹過兩次,隨著 5 月份谷歌 IO 的接近,新的 PlatformView
實現應該也會隨之而來,本次就從頭到尾來一個詳細的關于 PlatformView
的演進總結。
Flutter 作為新一代的跨平臺框架,通過自定義渲染引擎的創新大大提高了跨平臺的性能和一致性,但也正是因為這點, 相比之下 Flutter 在混合開發時對于原生控件的支持成本更高。
Flutter 混合開發的難點
首先 Flutter 在混合開發中最大的難點就在于它獨立的渲染引擎,舉一個不是很恰當的例子:
Flutter 里混合開發類似與把原生控件渲染到
WebView
里。
大致上在 Flutter 里混合開發的感覺就是這樣,因為 Flutter UI 不會轉換為原生控件,而是由 Flutter Engine 使用 Skia 直接渲染在 Surface
上。
所以 Flutter 在最早出來時并不支持 WebView
或 MapView
這些常用的控件,這也導致了當時 Flutter 一度的風評不大好,所以衍生出了第一代非官方的混合開發支持,例如: flutter_webview_plugin
。
在官方 WebView
控件支持出來之前 ,第三方是直接在 FlutterView
上覆蓋了一個新的原生控件,利用 Dart 中的占位控件來傳遞位置和大小。
Flutter 里幾乎所有渲染都是渲染到
FlutterView
這樣一個單頁面上,所以直接覆蓋一個新的原生WebView
只能說緩解燃眉之急。
如下圖,在 Flutter 端 push
出來一個 設定好位置和大小 的 SingleChildRenderObjectWidget
,從而得到需要顯示的大小和位置,將這些信息通過 MethodChannel
傳遞到原生層,在原生層 addContentView
一個指定大小和位置的 WebView
。
這樣看起來就像是在 Flutter 中添加了 WebView
,但實際這樣的操作只能說是“救急”,因為這樣的行為脫離了 Flutter 的渲染樹,其中一個問題就是:
當你跳轉 Flutter 其他頁面的時候會被當前原生的
WebView
擋?。徊⑶掖蜷_頁面的動畫時Appbar
和WebView
難以保持一致,因為Appbar
和WebView
是出于兩個動畫體系和渲染體系。
就比如打開了新的 Flutter UI 2 頁面,但是由于它還是在 FlutterView
內,所以它會被 WebView
所遮擋。
但是這個“占位”顯示的思路,也起到了一定的作用,在后續 Flutter 支持原生 PlatformView
上起到了帶頭的作用。
Flutter 初步支持原生控件
為了讓 Flutter 真正走向大眾化,官方開始推出了官方基于 PlatformView
的系列實現,比如: webview_flutter
,而這個實現 “縫縫補補” 也被沿用至今,成了 Flutter 接入原生的方式 之一。
Android
在 PlatformView
的整個實現中 Android 坑一直是最多的,因為一開始 Android 上主要是通過 AndroidView
做完成這項工作,而它的 Virtual Displays 實現其實并不友好。
在 Flutter 中會將 AndroidView
需要渲染的內容繪制到 VirtualDisplays
中 ,然后在 VirtualDisplay
對應的內存中,繪制的畫面就可以通過其 Surface
獲取得到。
VirtualDisplay
類似于一個虛擬顯示區域,需要結合DisplayManager
一起調用,一般在副屏顯示或者錄屏場景下會用到,在VirtualDisplay
里會將虛擬顯示區域的內容渲染在一個Surface
上。
如上圖所示,簡單來說就是原生控件的內容被繪制到內存里,然后 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
用于處理同步的問題。
因為當承載 AndroidView
的 SurfaceTexture
被釋放時,由于 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 接入原生控件:
- 通過
PlatformViewLink
的viewType
注冊了一個和原生層對應的注冊名稱,這和之前的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
) 把原生控件 addView
到 FlutterView
上,然后再通過 FlutterImageView
的能力去實現多圖層的混合。
不理解嗎?沒事,我們后面會詳細介紹,先簡單解釋就是:
Flutter 只直接通過原生的
addView
方法將PlatformView
添加到FlutterView
,這就不需要什么surface
渲染再去獲取的開銷,而當你還需要再PlatformView
上渲染 Flutter 自己的 Widget 時,Flutter 就會通過再疊加一個FlutterImageView
來承載這個 Flutter Widget 。
深入例子詳解
接下來讓我們從實際例子去理解 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),
),
)
],
)
此時我們添加一個通過 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),
),
)
],
)
可以看到,如上圖所示,在我們的顯示布局邊界上可以清晰看到它的信息:
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),
),
)
],
)
如上圖所示,可以看到布局和渲染效果正常,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),
),
)
],
)
可以看到紅色的 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),
),
)
],
)
可以看到雖然 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),
),
)
],
)
如上圖所示,可以看到兩個紅色的 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),
),
)
],
)
但是如果不在一個層級呢?我們調整兩個灰色 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),
),
)
],
)
可以看到,兩個紅色的 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),
),
),
),
],
)
可以看到運行后點擊能夠正常彈出 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),
),
),
),
),
],
)
運行之后可以看到,點擊沒有被覆蓋的灰色部分,還是可以彈出 Toast ,點擊紅色 RE
和灰色 RE
的交集處,可以正常彈出 SnackBar
。
所以可以看到 Hybrid Composition 上這種實現,能更原汁原味地保流下原生控件的事件和特性,因為從原生 角度,它就是原生層面的物理堆疊。
現在大家應該大致對于 Hybrid Composition 有了一定理解,那回到前面那個一開始 Layout InSpector 黑屏 ,后來又能渲染出界面的原因,這就和首次添加 Hybrid Composition 時多出來的 FlutterImageView
有關系。
如下圖所示,可以看到此時原生的灰色 RE
和 Flutter 的紅色 RE
是沒有交集的,為什么會多出來一個 FlutterImageView
呢?
這就需要說到 flutterView.convertToImageView()
這個方法。
在 Flutter 渲染 Hybrid Composition 的 PlatformView
時,會有一個 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
里的畫面提取出來,渲染到 FlutterImageView
的 onDraw
上。
所以回歸到前面的 flutterView.convertToImageView()
,在 Flutter 渲染 Hybrid Composition 的 PlatformView
時,會先把自己也變成了一個 FlutterImageView
,然后進入新的渲染流程:
- Flutter 在
onEndFrame
時,也就是每幀結束時,會判斷當前界面是否還有PlatformView
,如果沒有就會切換會默認的FlutterSurfaceView
; - 如果還存在
PlatformView
,就會調用acquireLatestImage
去獲取當前imageReader
里的畫面,得到新的currentBitmap
,然后觸發invalidate
。 -
invalidate
會導致FlutterSurfaceView
執行onDraw
,從而把currentBitmap
里的內容繪制出來。
所以我們搞清楚了 FlutterImageView
的作用,也搞清楚了為什么有了 Hybrid Composition 的 PlatformView
之后,在 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 Composition 的 PlatformView
的內容才能在 Layout Inspector 上看到,而 FlutterSurfaceView
看起來就是黑色空白。
問題
那 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 Composition 的 PlatformView
。
具體體現在 ImageReader 創建時,大于 29 的可以使用
HardwareBuffer
,而HardwareBuffer
允許在不同的應用程序進程之間共享緩沖區,通過HardwareBuffers
可以映射各種硬件系統的可訪問 memory,例如 GPU。
所以如果當 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
。
在這里 Virtual Display 沒有了,原本 Virtual Display 創建的 Surface 被設置到 PlatformViewWrapper
里面。
簡單介紹下:在 PlatformViewWrapper
里,會通過 surface.lockHardwareCanvas();
獲取到當前 Surface
的 Canvas
,并且通過 draw(surfaceCanvas)
傳遞給了 child
。
所以 child
的 UI 就被繪制到傳入的 Surface
上,而 Flutter Engine 根據 Surface
的 id 又可以獲取到對應的數據,通過一個不可視的 PlatformViewWrapper
完成了繪制切換而無需使用 VirtualDisplay
。
當然,目前在測試中接收到的反饋里有還不如以前的性能好,所以后續會如何調整還是需要看測試結果。
PS ,如果這個修改正式發布,可能 Flutter 的 Android miniSDK 版本就需要到 23 起步了。因為
lockHardwareCanvas()
需要 23 起,而不用兼容更低平臺的原因是lockCanvas()
屬于 CPU copy ,性能上會慢很多