Window 和 WindowManager
视频通话程序和音乐播放程序能看到的电话或音乐悬浮窗本质都是 Window,我们初始化空项目,应用启动时显示的画面就是 Window,系统状态栏和导航栏则也是 Window,所以单屏幕是可以对应多 Window 的,首先来看动态添加 Window 的过程
java
Button button = new Button(this);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
0, 0,
PixelFormat.TRANSPARENT
);
lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_SHOW_WHEN_LOCKED
lp.gravity = Gravity.LEFT | Gravity.TOP;
lp.x = 100;
lp.y = 100;
getWindowManager().addView(button, lp);
Window 是承载 View 的工具,所以添加 Window 的方法往往要伴随控件的加入,WindowManager 的 addView 方法会在参数设置的屏幕位置创建 Window,接着把 View 添加进 Window。
注意参数有 flags 和 type 两种属性,它们分别表示 Window 的显示特征和 Window 类型。常用的显示特征在代码块中有所提到:
- FLAG_NOT_FOCUSABLE:表示 Window 不需要获取焦点,也就意味着 Window 不需要也无法接收任何事件,事件会传给 z-order 更低且具有焦点的 Window,激活该标记会同时启用 FLAG_NOT_TOUCH_MODAL;
- FLAG_NOT_TOUCH_MODAL:添加的 Window 本身在高 z 区域,会默认拦截即使是 Window 外部区域的事件,该标志会将外部区域的事件分发到低 z 值 Window,若在 Window 内部则由自己处理;
- FLAG_SHOW_WHEN_LOCKED:表示允许 Window 显示在锁屏界面,常用于闹铃或电话功能。
type 即 Window 的类型:应用 Window、派生 Window 和系统 Window,应用 Window 与 Activity 实例对应;派生 Window 不能单独存在,需要依附在特定的父 Window;系统 Window 要在声明权限后才能创建,Toast 和系统状态栏都是系统 Window。
前面提到的 z-order 就是 Window 的分层策略,层级大的 Window 会覆盖在层级小的 Window 上面,不同的 Window 类型具有不同的层级 z 值范围,应用 Window 的层级范围为 1-99、派生 Window 的层级范围为 1000-1999、系统 Window 的层级范围为 2000-2999。层级值对应 type 参数,显然系统窗口的层级更大。
Window 本身是抽象类,其实现为 PhoneWindow。WindowManagerServer 存在于系统进程中,使用多进程管理当前显示屏幕的所有 Window。WindowManager 用于向屏幕添加 Window,其有 addView、updateViewLayout 和 removeView 等方法。Window 和 View 通过 ViewRootImpl 建立联系,向屏幕添加 Window 时我们也能发现,View 才是 Window 存在的实体,实际使用中无法直接访问 Window。
Window 的内部机制
addView
Window 的添加过程由 WindowManager 接口的实现类 WindowManagerImpl 实现,其直接转交给 WindowManagerGlobal 处理,removeView 和 updateViewLayout 也是相同的逻辑。
java
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
WindowManagerGlobal 的 addView 方法分有 3 步:
-
检查参数合法性,若是派生 Window 则根据父 Window 调整布局参数,类似 View 在 ViewGroup 内部的自适应处理;
-
创建 ViewRootImpl 并将 View 添加到列表,值得注意的是 WindowManagerGlobal 有若干成员列表,其分别是
- mViews:存储所有 Window 对应的 View;
- mRoots:存储所有 Window 对应的 ViewRootImpl;
- mParams:存储所有 Window 对应的布局参数;
- mDyingViews:存储正在被删除的 View 实例,可以认作已经调用 removeView 方法但删除操作还未完成的 Window 对象。
-
借助 ViewRootImpl 的 setView 方法更新界面并完成 Window 的添加过程。setView 会通过 requestLayout 完成异步刷新请求。
交由 ViewRootImpl 调用 requestLayout 方法后,mWindowSession 执行 Window 的添加过程,mWindowSession 是 Binder 对象,真正的实现类是 Session,最后会由 WindowManagerService 完成 Window 的添加。
removeView
Window 的删除过程也通过 WindowManagerImpl 后接着交由 WindowManagerGlobal 实现,其会做同步块查索引后再调用 removeViewLocked 方法
java
/* WindowManagerImpl */
public void removeView(View view, boolean immediate) {
...
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
...
}
}
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (defered) {
mDyingViews.add(view);
}
}
}
removeView 是异步删除,removeViewLocked 方法的处理主要有收起输入法、调用 ViewRootImpl.die 方法和将 View 添加到 mDyingViews 列表。die 方法内部只有简单判断,如果是异步删除则发送 MSG_DIE 消息,ViewRootImpl 的 Handler 会处理此消息并调用 toDie 方法。
最后由 dispatchDetachedFromWindow 方法执行真正的 View 删除逻辑,主要做有 4 件事:
- 垃圾回收工作,如清理数据和移除回调等;
- 通过 Session 的 remove 方法删除 Window;
- 调用 View 的 dispatchDetachedFromWindow 方法,内部会回调可供重写的 onDetachedFromWindow 和 onDetachedFromWindowInternal 方法;
- 调用 WindowManagerGlobal 的 doRemoveView 方法刷新数据,清除在列表的记录。
updateViewLayout
Window 的更新过程比较简单,还是由 WindowManagerGlobal 处理,它会更新 View 和 ViewRootImpl 的布局参数。ViewRootImpl 内部会通过 scheduleTraversals 方法对 View 重新布局,还会通过 WindowSession 更新 Window 视图,该过程同样是 IPC 过程,由 WindowManagerService 的 relayoutWindow 实现。