Flutter完整開發實戰詳解(二十、 Android PlatformView 和鍵盤問題)

戀貓de小郭發表於2020-02-24

作為系列文章的第二十篇,本篇將結合官方的技術文件科普 Android 上 PlatformView 的實現邏輯,並且解釋為什麼在 Android 上 PlatformView 的鍵盤總是有問題。

為什麼 iOS 上相對穩定,文中也做了對應介紹。

文章彙總地址:

Flutter 完整實戰實戰系列文章專欄

Flutter 番外的世界系列文章專欄

1、為什麼有 PlatformView

因為 Flutter 的實現在概念上類似於 Android 上的 WebView,Flutter 是通過將 Widget Tree 轉化為紋理後通過 Skia 實現控制元件繪製,這造就了優秀的跨平臺效果的同時,也帶來了不可逆的相容問題。

1.1、無法整合原生平臺控制元件

這就像 WebView 一樣,Flutter UI 不會轉換為 Android 控制元件,而是由 Flutter Engine 使用 Skia 直接在 SurfaceView 上渲染出來

這意味著預設情況下 Flutter UI 永遠不會包含 Android Native 的控制元件,也就是說無法在 Flutter 中整合如 WebViewMapView 這些常用的控制元件。

所以為解決這個問題,Flutter 建立了一個叫 AndroidView 的控制元件邏輯, 開發者使用該 Widget 可以將 Android Native 元件嵌入到 Flutter UI 中

1.2、AndroidView 的實現

AndroidView 這個 Widget 需要和 Flutter 相結合才能完整顯示:在 Flutter 中通過將 AndroidView 需要渲染的內容繪製到 VirtualDisplays 中 ,然後在 VirtualDisplay 對應的記憶體中,繪製的畫面就可以通過其 Surface 獲取得到

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

Flutter完整開發實戰詳解(二十、 Android  PlatformView 和鍵盤問題)

如上圖所示,簡單來說就是原生控制元件的內容被繪製到記憶體裡,然後 Flutter Engine 通過相對應的 textureId 就可以獲取到控制元件的渲染資料並顯示出來

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

1.3、 有其他可以實現的方式嗎?

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

所以這樣的好處就是:需要在“iOS平臺”檢視下方呈現的Flutter UI,最終會被繪製到其下方的紋理上;而需要在“平臺”上方呈現的Flutter UI,最終會被繪製在其上方的紋理。它們只需要在最後組合起來就可以了

通常這種方法更好,因為這意味著 Android Native View 可以直接新增到 Flutter 的 UI 層次結構中。

但是,Android 平臺並不支援這種模式,因為在 iOS 上框架渲染後系統會有回撥通知,例如:當 iOS 檢視向下移動 2px 時,我們也可以將其列表中的所有其他 Flutter 控制元件也向下渲染 2px

但是在 Android 上就沒有任何有關的系統 API,因此無法實現同步輸出的渲染。如果強行以這種方式在 Android 上使用,最終將產生很多如 AndroidView 與 Flutter UI 不同步的問題

有關此替代方法的詳細討論,詳見 flutter.dev/go/nshc

2、相關問題和解決方法

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

2.1、觸控事件

預設情況下, PlatformViews 是沒辦法接收觸控事件

因為 AndroidView 其實是被渲染在 VirtualDisplay 中 ,而每當使用者點選看到的 "AndroidView" 時,其實他們就真正”點選的是正在渲染的 Flutter 紋理 。使用者產生的觸控事件是直接傳送到 Flutter View 中,而不是他們實際點選的 AndroidView

2.1.1、解決方法

  • AndroidView 使用 Flutter Framework 中的點選測試邏輯來檢測使用者的觸控是否在需要特殊處理的區域內。

類似可見:《Flutter完整開發實戰詳解(十三、全面深入觸控和滑動原理)》

  • 當觸控成功時會向 Android embedding 傳送一條訊息,其中包含 touch 事件的詳細資訊。

  • Android embedding 中,該事件的座標最後會匹配到 AndroidViewVirtualDisplay 中的座標,然後會建立一個 MotionEvent 用於 描述觸控的新控制元件,並將其轉發到內部 VirtualDisplay 中真實的 AndroidView 中進行響應。

2.1.2、侷限性

  • 該實現邏輯會將新的 MotionEvent 直接分發給 AndroidView ,如果這個 View 又派生了其他檢視,那麼就可能會出現觸控資訊被髮送到錯誤的位置。

  • MotionEvent 的轉化過程中可能會因為機制的不同,存在某些資訊沒辦法完整轉化的丟失。

2.2、文字輸入

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

Android 目前不提供任何 API 來動態設定或更改的焦點 WindowFlutterfocusedWindow 通常是實際持有“真實的” Flutter 紋理和 UI ,並且對於使用者直接可見。

InputConnections(如何在 Android 中 輸入文字)在 unfocused 的 View 中通常是會被丟棄

2.2.1、解決方法

  • Flutter 重寫了 checkInputConnectionProxy 方法,這樣 Android 會認為 Flutter View 是作為 AndroidView 和輸入法編輯器(IME)的代理,這樣 Android 就可以從 Flutter View 中獲取到 InputConnections 然後作用於 AndroidView 上面。

  • 在 Android Q 開始 InputMethodManager(IMM)改為每個 Window 自己例項化而不是全域性單例。因此之前幼稚的“設定代理”的模式在 Q 開始不起作用。為了進一步解決這個問題,Flutter 建立了一個 Context 的子類, 該子類返回的內容與 Flutter View 中的 IMM 相同,這樣就不會需要在查詢 IMM 時需要返回的真實的 Window。這意味著當 Android 需要 IMM 時,VirtualDisplay 仍然會使用 Flutter View 的 IMM 作為代理。

  • 當要求 AndroidView 提供 InputConnection 時,它會檢查 AndroidView 是否確實是輸入的目標。如果是,那 AndroidView 中的 InputConnection 將被獲取並返回給 Android

  • Android 認為 Flutter View 是 focused 且可用的,因此 AndroidViewInputConnection 可以成功被獲取並使用。

2.2.2、 Platforview 中的 WebView 鍵盤輸入

在 Android N 之前的版本上 WebView 輸入比較複雜,因為它們具有自己內部的邏輯來建立和設定輸入連線,而這些輸入連線並沒有完全遵循 Android 的協議。在 flutter_webview 外掛中,還需要新增其他解決方法以便在可以在 WebView 啟用文字輸入。

2.2.3、侷限性

3、總結

PlatformView 的實現模式增加了 Flutter 的生命力和活力,但是相對的也引出了很多問題,比如 #webview-keyboard#webview#platform-views 相關的 issue 專題高居不下,並且如 webview_flutter 外掛的文件所述:

該外掛依賴 Flutter 的新機制來嵌入 Android 和 iOS 檢視。由於該機制當前處於開發人員預覽中,因此該外掛也應被視為開發人員預覽。

webview_flutter 的鍵盤支援也尚未準備好用於生產,因為 Webview 中的鍵盤支援目前還處於實驗性的階段。

所以到這裡相信你應該知道,為什麼 Flutter 中的 PlatforView 在 Android 上如此之難相容,並且鍵盤輸入問題會那麼多坑了

自此,第二十篇終於結束了!(///▽///)

資源推薦

Flutter完整開發實戰詳解(二十、 Android  PlatformView 和鍵盤問題)

相關文章