Window的内部机制
Window
是一个抽象的概念,每一个Window
都对应着一个View
和一个ViewRootImpl
,Window
和View
通过ViewRootImpl
来建立联系,因此Window
并不是实际存在的,它是以View
的形式存在。这点从WindowManager
的定义也可以看出,他提供的三个接口方法addView
,updateViewLayout
以及removeView
都是针对View
的,这说明View
才是Window
存在的实体。在实际使用中无法直接访问Window
,对Window
的访问必须通过WindowManager
。为了分析Window
的内部机制,这里从Window
的添加,删除以及更新说起。
8.2.1 Window的添加过程
Window
的添加过程需要通过WindowManager
的addView
来实现,WindowManager
是一个接口,它的真正实现是WindowManagerImpl
类。在WindowManagerImpl
中Window
的三大操作的实现如下:
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
可以发现,WindowManagerImpl
并没有直接实现Window
的三大操作,而是全部交给了WindowManagerGlobal
来处理,WindowManagerGlobal
以工厂的形式向外提供自己的实例,在WindowManagerGlobal
中如下一段代码:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
WindowManagerImpl
这种工作模式是典型的桥接模式,将所有的操作全部委托给WindowManagerGlobal
来实现。
1. 检查参数是否合法,如果是子Window那么还需要调整一些布局参数
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
2. 创建ViewRootImpl并将View添加到列表中
在WindowManagerGlobal
内部有如下几个列表比较重要:
private final ArrayList mViews = new ArrayList();
private final ArrayList mRoots = new ArrayList();
private final ArrayList mParams =
new ArrayList();
private final ArraySet mDyingViews = new ArraySet();
在上面的声明中,mViews
存储的是所有Window
所对应的View
,mRoots
存储的是所有Window
所对应的ViewRootImpl
,mParams
存储的是所有Window
所对应的布局参数,而mDyingViews
则存储了那些正在被删除的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
方法来完成,从第四章可以知道,View
的绘制过程是由ViewRootImpl
来完成的,这里当然也不例外,在setView
内部会通过requestLayout
来完成异步刷新请求。在下面的代码中,scheduleTraversals
实际是View
的绘制入口:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
接着会通过mWindowSession
最终来完成Window
的添加过程。mWindowSession
的类型是IWindowSession
,它是一个Binder
对象,真正的实现类是Session
,也就是Window
的添加过程是一次IPC
调用。
没找到代码
在Session
内部会通过WindowManagerService
来实现Window
的添加。
没找到代码
如此一来,Window
的添加请求就交给WindowManagerService
去处理了,在WindowManagerService
内部会为每一个应用保留一个单独的Session
。
8.2.2 Window的删除过程
Window
的删除过程和添加过程一样,都是先通过WindowManagerImpl
后,再进一步通过WindowManagerGlobal
来实现的。下面是WindowManagerGlobal
的removeView
的实现:
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
removeView
的逻辑很清晰,首先通过findViewLocked
来查找待删除的View
的索引,这个查找过程就是建立的数组遍历,然后再调用removeViewLocked
来做进一步的删除。
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 (deferred) {
mDyingViews.add(view);
}
}
}
removeViewLocked
是通过ViewRootImpl
来完成删除操作的。在WindowManager
中提供了两种删除接口removeView
和removeViewImmediate
,它们分别表示异步删除
和同步删除
,其中removeViewImmediate
使用起来需要特别注意,一般来说不需要使用此方法来删除Window
以免发生意外的错误。这里主要说异步删除的情况,具体的删除操作由ViewRootImpl
的die
方法来完成。在异步删除的情况下,die
方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View
并没有完成删除操作,所以最后会将其添加到mDyingViews
中,mDyingViews
表示待删除的View
列表。ViewRootImpl
的die
方法如下所示:
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
在die
方法内部只是做了简单的判断,如果是异步删除,那么就发送一个MSG_DIE
的消息,ViewRootImpl
中的Handler
会处理此消息并调用doDie
方法,如果是同步删除(立即删除),那么就不发消息直接调用doDie
方法,这就是这两种删除方式的区别。在doDie
内部会调用dispatchDetachedFromWindow
方法,真正删除View
的逻辑在dispatchDetachedFromWindow
方法的内部实现。dispatchDetachedFromWindow
方法主要做四件事:
- 垃圾回收相关的工作,比如清除数据和消息,移除回调。
- 通过
Sessio
n的remove
方法删除Window:mWindowSession.remove(mWindow)
,这同样是一个IPC
过程,最终会调用WindowManagerService
的removeWindow
方法。 - 调用
View
的dispatchDetachedFromWindow
,在内部会调用View
的onDetachedFromWindow()
以及onDetachedFromWindowInternal()
。对于onDetachedFromWindow()
大家一定不陌生,当View
从Window
中移除时,这个方法就会被调用,可以在这个方法的内部,做一些资源回收工作,比如终止动画,停止线程等。 - 调用
WindowManagerGlobal
的doRemoveView
方法刷新数据,包括mRoots
,mParams
以及mDyingViews
,需要将当前Window
所关联的这三类对象从列表中删除。
8.2.3 Window的更新过程
下面分析Window
的更新过程,还是要看WindowManagerGlobal
的updateViewLayout
方法。
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);
}
}
首先它需要更新View
的LayoutParams
并替换掉老的LayoutParams
,接着在更新ViewRootImpl
中的LayoutParams
,这一步是通过ViewRootImpl
的setLayoutParams
方法来实现的。在ViewRootImpl
中会通过scheduleTraversals
方法来对View
重新布局,包括测量,布局,重绘这三个过程。除了View
本身的重绘以外,ViewRootImpl
还会通过WindowSession
来更新Window
的视图,这个过程最终是由WindowManagerService
的relayoutWindow()
来具体实现的,它同样是一个IPC
过程。