Flutter SingleViewPresentation
在 Flutter 嵌原生视图能力的 Flutter Virtual displays 模式下,SingleViewPresentation 是 Android VirtualDisplay 的视图容器,平台视图既是在 SingleViewPresentation 内创建,也是由 SingleViewPresentation 触发创建。在本文中,将梳理 SingleViewPresentation 内部逻辑。对于 SingleViewPresentation 是如何被创建、使用的,参见《Flutter VirtualDisplayController》。
SingleViewPresentation 覆写了 WindowManager 的 addView/removeView/updateViewLayout 方法,使得向 WindowManager 中添加的视图,会被添加到 SingleViewPresentation 下。SingleViewPresentation 内部的视图结构如下:
rootView
/ \
/ \
/ \
container state.fakeWindowViewGroup
|
EmbeddedView
Presentation 与 Activity
Presentation 这个概念我之前了解较少,在 Android VirtualDisplay 下,使用 Presentation 进行视图展示。我将 Presentation 与 Activity 进行对比:
概念 | Activity | Presentation |
---|---|---|
用途 | Android 四大组件,表示整屏视窗 | 特殊的对话框,用于在第二屏幕上显示用户界面 |
生命周期 | 复杂,包括创建、启动、恢复、暂停、停止和销毁等状态 | 简单,只有创建、显示、隐藏 |
总之,都是有生命周期的视图容器。
核心成员
SingleViewPresentation 类的成员属性属下:
// 原生视图工厂
private final PlatformViewFactory viewFactory;
// 处理和分派无障碍事件
private final AccessibilityEventsDelegate accessibilityEventsDelegate;
// 监听焦点变化
private final OnFocusChangeListener focusChangeListener;
// 表示由Flutter框架分配给嵌入视图的视图ID。
private int viewId;
// 创建平台视图时的参数。
private Object createParams;
// Presentation 根视图
private AccessibilityDelegatingFrameLayout rootView;
private FrameLayout container;
// 存储与 SingleViewPresentation实例相关的状态信息
private final PresentationState state;
// 是否开始聚焦。
private boolean startFocused = false;
// 表示托管`FlutterView`的应用程序窗口的 Context
private final Context outerContext;
其中,比较重要的:
rootView
: 这是一个AccessibilityDelegatingFrameLayout
对象,它是 SingleViewPresentation 的根视图,包含两个子视图:一个包含嵌入视图的容器,和一个包含直接添加到演示的窗口管理器的视图的fakeWindowViewGroup
。container
: 这是一个FrameLayout
对象,当平台视图附加到演示时,它包含嵌入的平台视图(platformView.getView()
)。state
: 这是一个PresentationState
对象,它用于存储与SingleViewPresentation
实例相关的状态信息。
PresentationState
在类属性中,有一个类型为 PresentationState 内部类的属性 state
。在 Flutter Virtual displays 模式下,一旦 AndroidView 大小变化,Android VirtualDisplay 就会销毁重建,而依附于 Android VirtualDisplay 的 Presentation 也会重新创建,那原生视图怎么办?这就是 PresentationState 存在的意义,将在 Presentation 切换过程中,需要保留的状态封装起来(包括原生视图),在切换 Presentation 时,将 PresentationState 实例传递过去。
以下是对代码注释的解释:当在 Flutter 应用中嵌入的 Android 视图需要调整大小时,我们会将这个视图移动到一个新的虚拟显示设备上,这个新的虚拟显示设备具有新的大小。PresentationState
类用于存储与视图一起移动到新的虚拟显示设备的演示状态。
该类代码如下:
static class PresentationState {
// 在 Flutter 应用中嵌入的 Android 原生视图
private PlatformView platformView;
// 它是一个 InvocationHandler,用于代理 WindowManager。
// 这基本上是 presentation 的自定义 WindowManager。
private WindowManagerHandler windowManagerHandler;
// 它包含直接添加到窗口管理器的视图(例如`android.widget.PopupWindow`)。
private FakeWindowViewGroup fakeWindowViewGroup;
}
onCreate 方法
onCreate 方法是 Presentation 的初始化方法,用于创建视图布局:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
// 它包含直接添加到窗口管理器的视图
if (state.fakeWindowViewGroup == null) {
state.fakeWindowViewGroup = new FakeWindowViewGroup(getContext());
}
// 它是一个 InvocationHandler,用于代理 WindowManager。
// 这基本上是 presentation 的自定义 WindowManager。
if (state.windowManagerHandler == null) {
WindowManager windowManagerDelegate =
(WindowManager) getContext().getSystemService(WINDOW_SERVICE);
state.windowManagerHandler =
new WindowManagerHandler(windowManagerDelegate, state.fakeWindowViewGroup);
}
// 这是一个 FrameLayout 对象,当平台视图附加到演示时,它包含嵌入的平台视图
container = new FrameLayout(getContext());
// 替换系统的窗口管理器
Context context =
new PresentationContext(getContext(), state.windowManagerHandler, outerContext);
// 创建平台视图
if (state.platformView == null) {
state.platformView = viewFactory.create(context, viewId, createParams);
}
View embeddedView = state.platformView.getView();
container.addView(embeddedView);
rootView =
new AccessibilityDelegatingFrameLayout(
getContext(), accessibilityEventsDelegate, embeddedView);
rootView.addView(container);
rootView.addView(state.fakeWindowViewGroup);
embeddedView.setOnFocusChangeListener(focusChangeListener);
rootView.setFocusableInTouchMode(true);
// 启动时是否需要焦点
if (startFocused) {
embeddedView.requestFocus();
} else {
rootView.requestFocus();
}
setContentView(rootView);
}
其中:
- 创建了一个 PresentationContext 实例。
context
是一个PresentationContext
对象。PresentationContext
是一个自定义的ContextWrapper
,这个类的主要作用是替换系统的窗口管理器(WindowManager
)为我们自定义的实例。 - 也看到平台视图的最终创建
- 平台视图 embeddedView 被加入 container,container 被加入 rootView。
- 最终 rootView 作为 Presentation 的根视图
构造函数
SingleViewPresentation 有两个构造函数,这里看其中之一:
public SingleViewPresentation(
Context outerContext,
Display display,
PlatformViewFactory viewFactory,
AccessibilityEventsDelegate accessibilityEventsDelegate,
int viewId,
Object createParams,
OnFocusChangeListener focusChangeListener) {
// 初始化父类,并传入定制 ImmContext
super(new ImmContext(outerContext), display);
this.viewFactory = viewFactory;
this.accessibilityEventsDelegate = accessibilityEventsDelegate;
this.viewId = viewId;
this.createParams = createParams;
this.focusChangeListener = focusChangeListener;
this.outerContext = outerContext;
// 创建 state
state = new PresentationState();
getWindow()
.setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
}
}
其中,需要注意的是,在初始化父类时,传入了一个定制的 ImmContext,其中 Imm
表示输入法,表示其中有输入法相关定制逻辑。
ImmContext
注释含义:ImmContext
在构造时会缓存InputMethodManager
(IMM)的实例,并且不会获取任何后续的更改。在极少数情况下,如果Flutter视图更改了窗口,这将返回一个过时的实例。这个问题应该被修复,而不是延迟返回IMM,应该返回知道Flutter视图真实上下文的对象。
这里我联想到的场景是,Activity 中展示 Flutter 界面,Flutter 界面中嵌入 Android 视图,Android 视图中使用到输入法。如果该 Activity 在后台被回收,下次用户打开时,Activity 需要重新创建,这时 Android 视图中使用到输入法还是从旧的 Context 里拿到的,这可能会有问题。
PresentationContext
PresentationContext
是一个自定义的ContextWrapper
,它包装了基础的Context
对象,并添加了一些额外的功能。这个类的主要作用是替换系统的窗口管理器(WindowManager
)为我们自定义的实例。
以下是PresentationContext
类的主要功能:
-
替换窗口管理器:在
getSystemService(String name)
方法中,如果请求的系统服务是窗口服务(WINDOW_SERVICE
),那么这个方法将返回我们自定义的窗口管理器,而不是系统的窗口管理器。这是通过调用getWindowManager()
方法实现的。 -
处理警告对话框:如果我们检测到是
AlertDialog
的构造函数在请求窗口管理器,那么我们将返回应用程序窗口的窗口管理器,而不是我们自定义的窗口管理器。这是因为警告对话框显示在整个应用程序之上,不应该被限制在虚拟显示器上。这个功能是通过isCalledFromAlertDialog()
方法和getSystemService(String name)
方法中的条件判断实现的。 -
缓存窗口管理器:在
getWindowManager()
方法中,我们会缓存我们自定义的窗口管理器,以便在后续的请求中重复使用。这是通过检查windowManager
是否为null
,如果为null
,则调用windowManagerHandler.getWindowManager()
来获取窗口管理器实现的。
SingleViewPresentation
是一个内部类,它继承自Presentation
类。Presentation
类是Android中用于在不同的显示设备上显示不同内容的类。在这个情况下,SingleViewPresentation
被用来在虚拟显示设备上显示一个单一的视图。
以下是SingleViewPresentation
的核心部分:
-
构造函数:它接收一系列参数,包括上下文、显示设备、视图工厂、无障碍事件代理、视图ID、创建参数和焦点变化监听器。这些参数用于初始化
SingleViewPresentation
的实例。 -
onCreate
方法:当Presentation
创建时,这个方法会被调用。在这个方法中,它首先调用父类的onCreate
方法,然后创建一个新的PresentationState
实例,并将其赋值给presentationState
属性。然后,它使用视图工厂创建一个新的PlatformView
实例,并将其赋值给presentationState.platformView
。最后,它将PlatformView
的视图添加到Presentation
的内容视图中。 -
onStop
方法:当Presentation
停止时,这个方法会被调用。在这个方法中,它首先调用PlatformView
的dispose
方法,然后调用父类的onStop
方法。 -
onStart
方法:当Presentation
开始时,这个方法会被调用。在这个方法中,它首先调用父类的onStart
方法,然后调用PlatformView
的onFlutterViewAttached
方法。 -
onDisplayRemoved
方法:当显示设备被移除时,这个方法会被调用。在这个方法中,它首先调用PlatformView
的dispose
方法,然后调用父类的onDisplayRemoved
方法。 -
detachFromFlutterEngine
方法:这个方法用于从Flutter引擎中分离PlatformView
。在这个方法中,它首先调用PlatformView
的onFlutterViewDetached
方法,然后将presentationState
设置为null
。
网络资源
- Android Presentation多屏显示 - RichardLM - 博客园
- Android-Presentation双屏异显-一看就懂篇_android presentation-CSDN博客
本文作者:Maeiee
本文链接:Flutter SingleViewPresentation
版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!
喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!