Flutter WebView 下无法长按粘贴的问题
解决过程
如题,在使用 Flutter WebView 过程中,测试反馈了一个 Web 页面无法长按出现「粘贴」按钮的问题。
当我看到这个问题时,第一反应是 WebView 的开源库可能有 bug,查了相关 issue,发现在 Flutter 中嵌 Native View 的水有点深,后续扩展详聊。
这里先记录解决问题思路和步骤:
-
官方提出 Android 下需要 Flutter 1.22 版本之后使用 Hybrid composition 模式来嵌入 View 才不会导致部分键盘处理和辅助功能丢失,由于我的应用场景是 WebView,不可能抛弃键盘相关的功能,所以只能这么干
-
我使用的 WebView 库是 inappwebview,支持 Hybrid composition 得是 master 分支的 5.0.0 版本,但是作者消失了一段时间,迟迟没有发布新版本,我考虑直接使用 git master 分支作为依赖源(如果作者还玩失踪,甚至有点想自己维护一个 flutter webview 库)。
-
参考 inappwebview 文档,Android 下添加了
useHybridComposition
配置,但是依旧无法长按出现「粘贴」按钮 -
我拿官方的 webview_flutter 和 inappwebview 分别写了一个最小化的 Demo,发现都能长按出现「粘贴」按钮,那就是我项目里边代码的问题了
-
长按是手势事件,项目中代码和最小化的 Demo 中关于这一部分的差异就是我们 Flutter 页面最外层加了一个
GestureDetector
来简化切换页面时的键盘收起问题,我尝试去掉这个组件问题就解决了 -
最终问题就是这个
GestureDetector
为什么会拦截掉 Native View 的长按事件,查了一通文档,发现 AndroidView/PlatformViewSurface/UiKitView 如果外层有GestureDetector
之类的手势事件监听,并且要参与手势事件竞争的话,需要配置gestureRecognizers
-
最后就解决了,根据官方文档,传入对应的配置:
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[ new Factory<OneSequenceGestureRecognizer>( () => EagerGestureRecognizer()) ].toSet()
EagerGestureRecognizer 基本就是提供用来给 AndroidView/PlatformViewSurface 声明要抢占各种手势事件的了。
下边写一下这次 debug 过程中了解到的 Flutter UI 嵌入原生 View 的一些东西。
Flutter 内嵌 Native View
Flutter 允许我们在 Flutter 的 UI 界面上嵌入原生的 View,参考官网的指引:
Hosting native Android and iOS views in your Flutter app with Platform Views
我们使用 Flutter WebView 时,也就是在我们的 Flutter UI 页面中嵌入了一个 WebView 的 View。这已经涉及原生层面的 View 了,所以在 iOS 和 Android 实现上一定有所不同。
WebView 的实现上,iOS 走 WKWebView,Android 走 android.webkit.WebView,在 Flutter 嵌入原生 View 的实现上,两端也有比较大区别:
Android 在 Flutter UI 嵌入原生 View 时有两种实现方式,具体的实现步骤参考官网文档,这里关注的是它们之间的区别:
-
Virtual displays
这个模式会将 android.view.View 渲染为纹理,主要是 AndroidView 使用 TextureLayer 来实现,它不会嵌入到 Android Activity 的视图层次结构中,某些原生的交互,如键盘处理和辅助功能可能会无法工作。
官方性能部分提及,这个模式可能会导致浪费显存和绘制性能,但是由于不和其他线程竞争,在处理动画上有优势。
所以当你的应用使用原生 View 是纯展示类型的,可以考虑使用该模式
-
Hybrid composition
在 Flutter 1.22 版本之后,支持将 android.view.View 集成到视图层次结构中,对于键盘处理和辅助功能是完整支持的,但是在 Android 10 版本之前,可能会降低 UI 帧吞吐量。
iOS 只有一种方式,就是 Hybrid composition,直接将 UIView 嵌入到视图层级中,简单暴力。官网文档也稍有提及,在 Flutter UI 中嵌入 View 是比较昂贵的操作,会损耗性能。
优化方案
如果 Android 10 版本之前使用 Hybrid composition,或者 iOS 的 View 真的出现 Flutter 嵌入 View 导致的相关性能问题时怎么办?
暂时想到的解法就是抛弃在 Flutter UI 中嵌入 WebView 的做法,可以考虑:
- 反过来实现,用一个 Android Native View 嵌我们需要的 Flutter UI 部分
- 如果 Flutter UI 部分内容不多,可以考虑整个 View 都使用 Native 实现,例如你只是要打开个定制的 Web 页面这种业务场景