Android进阶知识(二十):理解Window和WindowManager
Window表示一个窗口的概念,其实际上是View的直接管理者;它是一个抽象类,具体实现是PhoneWindow。Window的创建可以通过WindowManager来完成,WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。
一、Window和WindowManager
通过WindowManager添加Window的代码如下。
mFloatingButton = new Button(this);
mFloatingButton.setText("button");
mLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParms.WRAP_CONTENT,
0, 0, PixelFormat.TRANSPARENT);
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mWindowManager.addView(mFloatingButton, mLayoutParams);
上述代码将一个Button添加到屏幕(100,300)的位置上。其中WindowManager.LayoutParams中的flags参数表示Window属性,type表示Window类型。flags常见属性如下表所示。
flags参数选项 | 介绍 |
---|---|
FLAG_NOT_FOCUSABLE | 表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window |
FLAG_NOT_TOUCH_MODAL | 系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。 |
FLAG_SHOW_WHEN_LOCKED | 开启此模式可以让Window显示在锁屏的界面上 |
Window类型有三种,分别为应用Window、子Window和系统Window。相对应的Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window上面。具体见下表。
Window类型 | 描述 | 层级等级 |
---|---|---|
应用Window | 对应一个Activity | 层级范围:1~99 |
子Window | 不能单独存在,需要附属在特定的父Window之中,例如Dialog | 层级范围:1000~1999 |
系统Window | 需要声明权限才能创建的Window,比如Toast和系统状态栏 | 层级范围:2000~2999 |
需要注意的是,系统类型的Window是需要检查权限的,需要添加权限:。
WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。WindowManager操作Window的过程更像是在操作Window中的View。
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
二、Window的内部机制
Window是一个抽象的概念,每个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,View才是Window存在的实体。
在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager。为了分析Window内部机制,从Window的添加、删除以及更新说起。
- Window的添加过程
Window的添加过程需要通过WindowManager的addView实现,WindowManager是一个接口,其真正实现是WindowManagerImpl类。在该类中Window的三大操作全部交给WindowManagerGlobal来处理,这种模式叫做桥接模式。
WindowManagerGlobal的addView方法主要分为如下几步:
1)检查参数是否合法,如果是子Window那么还需要调整一些布局参数
2)创建ViewRootImpl并将View添加到列表中
在WindowManagerGlobal内部有几个列表变量比较重要,如下表所示。
列表 | 类型 | 存储内容 |
---|---|---|
mViews | ArrayList<View> | 所有Window所对应的View |
mRoots | ArrayList<ViewRootImpl> | 所有Window所对应的ViewRootImpl |
mParams | ArrayList<WindowManager.LayoutParams> | 所有Window所对应的布局参数 |
mDyingViews | ArrayList<View> | 存储正在被删除的View对象,或者说已经调用removeView方法但是删除操作还未完成的Window对象 |
在addView中通过如下方式将Window的一系列对象添加到列表中。
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
3)通过ViewRootImpl来更新界面并完成Window的添加过程
该步骤通过ViewRootImpl的setView完成,在setView内部会调用requestLayout来完成异步刷新请求。具体流程如下。
由此可见Window的添加过程是一次IPC调用,最终Window的添加请求会交给WindowManagerService处理。
- Window的删除过程
Window的删除过程和添加过程一致,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGlobal实现。具体流程如下。
removeViewLocked是通过ViewRootImpl的die方法来完成的。在WindowManager中提供了两种删除接口removeView(异步删除)和removeViewImmediate(同步删除,一般不使用)。
在异步删除的情况下,die方法只是发送一个MSG_DIE的消息就立刻返回了,这个时候View并没有完成删除操作,所以最后会将其添加到mDyingViews中。
boolean die(boolean immediate) {
// 如果我们在遍历中途,请确保立即执行,
// 否则dispatchDetachedFromWindow造成的损坏将在返回时造成破坏
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(...);
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
两种删除方法的区别在于,异步删除只是发送一个MSG_DIE消息,Handler会处理此消息并调用doDie方法;而同步删除不发消息直接调用doDie方法。在doDie方法内部会调用dispatchDetachedFromWindow方法,这是真正的删除逻辑所在,其主要任务如下:
- 垃圾回收相关的工作。
- 通过Seesion的remove方法删除Window:IPC过程,最终调用WindowManagerService的removeView方法。
- 调用View的dispatchDetachedFromWindow方法,在内部会调用View的onDetachedFromWindow以及onDetachedFromWindowInternal。
- 调用WindowManagerGlobal的doRemoveView方法刷新数据,包括mRoots、mParams以及mDyingViews。
- Window的更新过程
Window的更新过程由WindowManagerGlobal的updateViewLayout实现,其逻辑比较简单,首先更新View的LayoutParams并替换掉老的LayoutParams,接着更新ViewRootImpl中的LayoutParams(ViewRootImpl.setLayoutParams)。
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
在ViewRootImpl中通过scheduleTraversals方法对View重新进行测量、布局、重绘,除此之外,还会通过WindowSession来更新Window的视图,这个过程最终通过WindowManagerService的relayoutWindow()来实现,同样是一个IPC过程。
参考资料:《Android开发艺术探索》