前面两节addView_1和addView_2在代码上分析了整个addWindow的流程,并且对其排序算法进行了解析,但是这里还是差了个宏观上的流程的分析,所以本节主要讲一下我对整个窗口逻辑的理解。
1、 整个屏幕上窗口的排序:
在前一节讲了窗口的排序逻辑,分别是应用窗口、系统窗口、和子窗口,但是这里讲的比较散,没有一个宏观的概念。 WMS里窗口的存在形式是WindowState,那WMS要想访问各个窗口是不是得将他们存储起来? 于是可以得出下图关系:
这里IBinder就是窗口在ViewRootImpl中W类的对象。
2、 对三个类型窗口的一些举例:
Activity 对应的窗口是应用窗口;PopupWindow,ContextMenu,OptionMenu 是常用的子窗口;像 Toast 和系统警告提示框(如ANR)就是系窗口,还有很多应用的悬浮框也属于系统窗口类型;
3、DisplayContent的引出
在屏幕上显示窗口的时候需要有一个先后顺序,所以对WindowState也会有一定的排序,而且在使用窗口的时候,还会把相同类型的窗口聚类到一块,通过一个WindowState的容器进行统一的管控,即统一的类WindowToken,不过WindowToken是系统窗口的聚类,应用窗口的聚类则是ActivityRecord。WindowToken是ActivityRecord的父类,并继承自WindowContainer类,WindowContainer类中有一个成员mChildren,其类型为WindowList(就是一个存放窗口的ArrayList)。子窗口因为寄生在父窗口上,而父窗口也只能是系统窗口或者应用窗口(此处暂时不讨论几个其他类型窗口)。这下好了,那我们的屏幕上就剩下两个大类型的窗口了:ActivityRecord集群和WindowToken集群,在上一节中也详细的讨论了在同一应用窗口、子窗口和系统窗口内部如何排序的。但是现在屏幕上要是同时出现了多个应用窗口又该如何排序?
所以这里还需要一个大的概念把应用窗口和系统窗口都包含进来,然后在内部再构建一个排序算法。
这里就根据获取WindowManagerService中的获取token的代码找到一个新的类,DisplayContent。
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
从这里可见,其实所有的Token都是存储在DisplayContent中,而DisplayContent又也是继承子WindowContainer的类。 故这个DisplayContent很有可能就是这么一个能存储应用窗口和系统窗口的大类,以便于管理整个屏幕上的所有窗口。 这样的话在屏幕上有多个系统窗口或者应用窗口时就也能进行排序了。
4、关于DisplayContent的介绍:
关于DisplayContent中排序的算法,最好的方法就是直接看代码。要想看其中WindowToken的排序算法,就直接看到addWindowToken函数。
void addWindowToken(IBinder binder, WindowToken token) {
final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
if (dc != null) {
throw new IllegalArgumentException("Can't map token=" + token + " to display="
+ getName() + " already mapped to display=" + dc + " tokens=" + dc.mTokenMap);
}
if (binder == null) {
throw new IllegalArgumentException("Can't map token=" + token + " to display="
+ getName() + " binder is null");
}
if (token == null) {
throw new IllegalArgumentException("Can't map null token to display="
+ getName() + " binder=" + binder);
}
mTokenMap.put(binder, token);
//这里看到只会对应用窗口排序
if (token.asActivityRecord() == null) {
token.mDisplayContent = this;
final DisplayArea.Tokens da = findAreaForToken(token).asTokens();
//下面就是添加的算法,添加的时候自然要排序
da.addChild(token);
}
}
然后往上寻找addChild方法的实现。在DisplayArea中会有addChild方法的实现。
void addChild(WindowToken token) {
//熟悉的比较算法
addChild(token, mWindowComparator);
}
所以最后实现的还是WindowContainer的addChild方法,不过比较器不太一样,再看看比较器的实现:
private final Comparator<WindowToken> mWindowComparator =
Comparator.comparingInt(WindowToken::getWindowLayerFromType);
这个比较器的参数比较特别,这个是lambda表达式,::的功能其实和c++类似,访问该类中的方法,现在先看比较器的实现。
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
其实方法比较简单,就是对比两个参数的大小。
所以再看函数的参数,通过寻找WindowToken中的getWindowLayerFromType()方法,可看到实现如下:
int getWindowLayerFromType() {
return mWmService.mPolicy.getWindowLayerFromTypeLw(windowType, mOwnerCanManageAppTokens,
mRoundedCornerOverlay);
}
default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow) {
return getWindowLayerFromTypeLw(type, canAddInternalSystemWindow,
false /* roundedCornerOverlay */);
}
default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,
boolean roundedCornerOverlay) {
// Always put the rounded corner layer to the top most.
if (roundedCornerOverlay && canAddInternalSystemWindow) {
return getMaxWindowLayer();
}
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
return APPLICATION_LAYER;
}
switch (type) {
case TYPE_WALLPAPER:
return 1;
..............................
case TYPE_POINTER:
// the (mouse) pointer layer
return 35;
default:
Slog.e("WindowManager", "Unknown window type: " + type);
return 3;
}
}
上述函数就是返回一个int型参数,综合比较器就是对比该返回值的大小进行排序。所以这里也看出来了对比的方法实现了。
然后系统窗口我理解是因为系统窗口在type分类的时候就已经排序好了,所以不用再排序了吧。下面是DisplayContent类的继承关系:
5、 总结:
所以综上我们可以得到下面这个图,然后添加窗口的时候根据窗口的类型选择他所在的地方,根据排序算法插入到合适的位置就行了。