作為系列文章的第二十篇,本篇將結合官方的技術文件科普 Android 上 PlatformView
的實現邏輯,並且解釋為什麼在 Android 上 PlatformView
的鍵盤總是有問題。
為什麼 iOS 上相對穩定,文中也做了對應介紹。
文章彙總地址:
1、為什麼有 PlatformView
因為 Flutter 的實現在概念上類似於 Android 上的 WebView
,Flutter 是通過將 Widget Tree
轉化為紋理後通過 Skia 實現控制元件繪製,這造就了優秀的跨平臺效果的同時,也帶來了不可逆的相容問題。
1.1、無法整合原生平臺控制元件
這就像 WebView 一樣,Flutter UI 不會轉換為 Android 控制元件,而是由 Flutter Engine 使用 Skia 直接在 SurfaceView
上渲染出來。
這意味著預設情況下 Flutter UI 永遠不會包含 Android Native 的控制元件,也就是說無法在 Flutter 中整合如 WebView
或 MapView
這些常用的控制元件。
所以為解決這個問題,Flutter 建立了一個叫 AndroidView
的控制元件邏輯, 開發者使用該 Widget 可以將 Android Native 元件嵌入到 Flutter UI 中。
1.2、AndroidView 的實現
AndroidView
這個 Widget 需要和 Flutter 相結合才能完整顯示:在 Flutter 中通過將 AndroidView
需要渲染的內容繪製到 VirtualDisplays
中
,然後在 VirtualDisplay
對應的記憶體中,繪製的畫面就可以通過其 Surface
獲取得到。
VirtualDisplay
類似於一個虛擬顯示區域,需要結合DisplayManager
一起呼叫,一般在副屏顯示或者錄屏場景下會用到。VirtualDisplay
會將虛擬顯示區域的內容渲染在一個Surface
上。
如上圖所示,簡單來說就是原生控制元件的內容被繪製到記憶體裡,然後 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 中的點選測試邏輯來檢測使用者的觸控是否在需要特殊處理的區域內。
-
當觸控成功時會向 Android embedding 傳送一條訊息,其中包含 touch 事件的詳細資訊。
-
在 Android embedding 中,該事件的座標最後會匹配到
AndroidView
在VirtualDisplay
中的座標,然後會建立一個MotionEvent
用於 描述觸控的新控制元件,並將其轉發到內部VirtualDisplay
中真實的AndroidView
中進行響應。
2.1.2、侷限性
-
該實現邏輯會將新的
MotionEvent
直接分發給AndroidView
,如果這個 View 又派生了其他檢視,那麼就可能會出現觸控資訊被髮送到錯誤的位置。 -
MotionEvent
的轉化過程中可能會因為機制的不同,存在某些資訊沒辦法完整轉化的丟失。
2.2、文字輸入
通常,AndroidView
是無法獲取到文字輸入,因為 VirtualDisplay
所在的位置會始終被認為是 unfocused
的狀態。
Android 目前不提供任何 API 來動態設定或更改的焦點 Window
,Flutter
中focused
的 Window
通常是實際持有“真實的” 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
且可用的,因此AndroidView
的InputConnection
可以成功被獲取並使用。
2.2.2、 Platforview 中的 WebView 鍵盤輸入
在 Android N 之前的版本上 WebView
輸入比較複雜,因為它們具有自己內部的邏輯來建立和設定輸入連線,而這些輸入連線並沒有完全遵循 Android 的協議。在 flutter_webview
外掛中,還需要新增其他解決方法以便在可以在 WebView
啟用文字輸入。
- 設定一個代理 View ,該 View 與
WebView
在相同的執行緒上偵聽輸入連線。如果沒有此功能,WebView
將在內部消耗所有InputConnection
的呼叫,而不會通知 Flutter View 代理。 - 在代理執行緒中,返回 Flutter View 以建立輸入。。
WebView
失去焦點時,將輸入連線重置回 Flutter 執行緒。這樣可以防止文字輸入“卡”在 WebView 內。
2.2.3、侷限性
-
通常這個邏輯取決於 Android 的內部行為,並且可能會十分脆弱,比如: 1.12 版本下針對華為等裝置出現的鍵盤輸入異常等問題。
-
某些文字功能仍然不可用,例如:“複製”和“共享”對話方塊當前不可用。
3、總結
PlatformView
的實現模式增加了 Flutter 的生命力和活力,但是相對的也引出了很多問題,比如 #webview-keyboard、#webview、#platform-views 相關的 issue 專題高居不下,並且如 webview_flutter 外掛的文件所述:
該外掛依賴 Flutter 的新機制來嵌入 Android 和 iOS 檢視。由於該機制當前處於開發人員預覽中,因此該外掛也應被視為開發人員預覽。
webview_flutter
的鍵盤支援也尚未準備好用於生產,因為 Webview 中的鍵盤支援目前還處於實驗性的階段。
所以到這裡相信你應該知道,為什麼 Flutter 中的 PlatforView
在 Android 上如此之難相容,並且鍵盤輸入問題會那麼多坑了。
自此,第二十篇終於結束了!(///▽///)
資源推薦
- Github : github.com/CarGuo
- 開源 Flutter 完整專案:github.com/CarGuo/GSYG…
- 開源 Flutter 多案例學習型專案: github.com/CarGuo/GSYF…
- 開源 Fluttre 實戰電子書專案:github.com/CarGuo/GSYF…
- 開源 React Native 專案:github.com/CarGuo/GSYG…