关联文章:1和2,可综合阅读便于理解。
在详解WindowManager的addView方法之前还需要的单独看下一些基础概念:
1、 WindowState:
WindowState是在WMS侧,窗口的体现,前面已经说过了,在应用侧,窗口的体现是PhoneWindow。可以查看WindowState.java文件。
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
InsetsControlTarget, InputTarget {
final Session mSession;//每个WindowState都有专门的和WMS会话的session,一个进程的所有window公用一个session;
final IWindow mClient;//这里是addView方法时传进来的ViewRootImpl中定义的IWindow的binder,用于WMS访问应用侧的窗口。
@NonNull WindowToken mToken;//窗口的WindowToken属性,下面会介绍
ActivityRecord mActivityRecord;//看定义是和mToken相关联的一个属性
final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams();//窗口的参数
private boolean mIsChildWindow;//是否是子窗口
final int mBaseLayer;//窗口在wms中的排序依据
final int mSubLayer;//子窗口专属的排序依据
}
2、 WindowToken:
WindowToken就是一个集合,元素是WindowState。其定义如下:
class WindowToken extends WindowContainer<WindowState> {
final IBinder token;
final int windowType;
}
在这里可以看到两个属性,token和windowType,其中token属性是一个binder对象,具有系统唯一性,所以在WMS的mWindowMap中以此变量作为key,作为WindowState的索引。token属性其实就是viewRootImpl中的IWindow接口(一个AIDL接口)在WMS的远端代理,WMS可以通过IWindow访问应用侧的窗口属性,并通过ViewRootImpl进行操作。
windowType就是Windowtoken这个集合的窗口的类型了,和WindowManager.LayoutParams相同。
中的
3、 Token:
上面已经解释了token是什么了。这里讲下Binder的大概关系吧,继承关系如下图:后面可能会开一个专栏讲binder,这个类太复杂了。
IBinder是基类,Binder类存在于IPC通信的server端,BinderProxy则处于Client端,在上面的WMS和ViewRootImpl中,如果以WMS为client的话,那么BinderProxy就是token,而对应的Binder就是ViewRootImpl中的W。
4、 mActivityRecord:
这个属性和WindowToken之间能相互转化,如果WindowState的类型是应用窗口,那么该属性就和mToken相同。如果是非应用窗口的话,这里就是null。
5、 mBaseLayers:这里可以看到构造函数内的赋值:
mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
/
default int getWindowLayerLw(WindowState win) {
return getWindowLayerFromTypeLw(win.getBaseType(), win.canAddInternalSystemWindow());
}
//此函数主要功能就是返回一个基数,用于计算窗口在Z轴上的参数
default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,
boolean roundedCornerOverlay) {
// 这里就是把某类窗口直接放在了最顶层.
if (roundedCornerOverlay && canAddInternalSystemWindow) {
return getMaxWindowLayer();
}
//下面就是将应用窗口类型的窗口返回一个基数
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
return APPLICATION_LAYER;
}
//为不同的type的窗口返回不同的基数
switch (type) {
case TYPE_WALLPAPER:
// wallpaper is at the bottom, though the window manager may move it.
return 1;
………………………………………………………………………
这里省略了一堆的type的case,简单解释就是根据不同的系统窗口类型返回不同的基数
………………………………………………………………………
default:
Slog.e("WindowManager", "Unknown window type: " + type);
return 3;
}
}
窗口在屏幕上显示,肯定会有重叠,那谁遮住谁呢?谁能在顶层显示呢?就靠z轴参数,就是这里的mBaseLayer,通过该参数可确定窗口在z轴的高度,从而决定谁能遮住谁,或者输入事件分发给谁。
当然了窗口类型分为四类:
应用窗口:顾名思义,应用的界面显示窗口,这类窗口用来显示Activity和对话框,是通过在onCreate方法中的setContentView创建的。分类对应ActivityRecord。
子窗口 :就是其他窗口的子窗口,通常用于显示一些特定的功能,比如弹出菜单,对话框,浮动窗口等,属性啥的都是依赖他的父窗口。可通过AlertDialog的实例化对象通过show()方法添加,该对象可通过AlertDialog.Builder 的实例调用create()方法获取。
系统窗口:系统窗口用于显示系统级信息、控制界面元素和系统通知。它们不属于应用程序的一部分,而是由Android系统管理。这类窗口还有个特点就是系统会直接给他们添加token。分类对应WindowToken
一些其他窗口:toast、悬浮窗、键盘窗口等。
在上面就可得出,子类的mBaseLayers就是其父类的mBaseLayers。
6、 mSubLayer:
该参数只针对子窗口,父窗口已经进行高度的排序了,那子窗口不也得有个高低贵贱之分?所以就通过该参数进行计算。
mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
//函数比较简单,直接通过几个case就搞定了
default int getSubWindowLayerFromTypeLw(int type) {
switch (type) {
case TYPE_APPLICATION_PANEL:
case TYPE_APPLICATION_ATTACHED_DIALOG:
return APPLICATION_PANEL_SUBLAYER;
case TYPE_APPLICATION_MEDIA:
return APPLICATION_MEDIA_SUBLAYER;
case TYPE_APPLICATION_MEDIA_OVERLAY:
return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
case TYPE_APPLICATION_SUB_PANEL:
return APPLICATION_SUB_PANEL_SUBLAYER;
case TYPE_APPLICATION_ABOVE_SUB_PANEL:
return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;
}
Slog.e("WindowManager", "Unknown sub-window type: " + type);
return 0;
}
这样就计算出了子窗口高度。
7、 子窗口的排序:
子窗口既然是属于父窗口的,那么就得有个添加的入口,所以找到了在WindowState构造函数中的这个地方:
parentWindow.addChild(this, sWindowSubLayerComparator);
这里可以看到addChild函数,发现WindowState中没有?那怎么办?那不就 得找他爹了呗,继续看到WindowContainer类中,找到了addChild函数:
protected void addChild(E child, Comparator<E> comparator) {
//这里是处理异常,暂时不看
if (!child.mReparenting && child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
//这里才是排序的核心
/*
循环遍历下父类WindowState的所有mChildren,然后通过对比comparator
决定子窗口的排序,这里的排序方法不用说要么是比谁大,要么是比谁小
*/
int positionToAdd = -1;
if (comparator != null) {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (comparator.compare(child, mChildren.get(i)) < 0) {
positionToAdd = i;
break;
}
}
}
//如果发现上面的遍历都没找到合适的位置插入该子窗口,那就放到最后
if (positionToAdd == -1) {
mChildren.add(child);
} else {
//如果找到了合适的位置,就将子窗口放到positionToAdd这个为止
mChildren.add(positionToAdd, child);
}
//将子窗口与父窗口相关联
child.setParent(this);
}
这里要说下,WindowState的mChildren是一个存放窗口的ArrayList。然后再看下比较器的实现:
private static final Comparator<WindowState> sWindowSubLayerComparator =
new Comparator<WindowState>() {//构建一个WindowState的比较器
@Override
//重写对比函数
public int compare(WindowState w1, WindowState w2) {
//在这里可以看出其实就是对比子窗口的SubLayer参数的大小,
//如果1小于2就返回-1
final int layer1 = w1.mSubLayer;
final int layer2 = w2.mSubLayer;
if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
// We insert the child window into the list ordered by
// the sub-layer. For same sub-layers, the negative one
// should go below others; the positive one should go
// above others.
return -1;
}
return 1;
};
};
从上面就可以看出,父窗口中还维护了一个ArrayList,专门用来存放子窗口,其中子窗口在ArrayList中还是根据mSubLayer进行排序的。当然子窗口中也会有个属性指向他们的父窗口。非子窗口就只有mBaseLayer了,其mSubLayer就直接是0。
8、 非子窗口的排序:
看到了子窗口的存放就会想到那非子窗口呢?非子窗口也会有他的排序场景,想象下,我们在之前说到过,同类的window会放进同一个WindowToken中,那他们是不是也要排序? 所以子窗口是在初始化WindowState的时候就进行了排序,非子窗口会稍晚一点,在WMS的addWindow()方法中的这里进行排序:
win.mToken.addWindow(win);
所以看到WindowToken中的addWindow函数,这里可以发现也会调用到addChild函数(WindowToken也是继承自WindowContainer的),只不过比较器的实现不同:
void addWindow(final WindowState win) {
ProtoLog.d(WM_DEBUG_FOCUS,
"addWindow: win=%s Callers=%s", win, Debug.getCallers(5));
if (win.isChildWindow()) {
// 如果是子窗口已经放进了父窗口的ArrayList中了。
return;
}
// 为该窗口创建一个新的surface用于绘制窗口
if (mSurfaceControl == null) {
createSurfaceControl(true /* force */);
reassignLayer(getSyncTransaction());
}
//如果不是子窗的话,就会调用addChild方法,将其放入WindowToken中
if (!mChildren.contains(win)) {
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", win, this);
addChild(win, mWindowComparator);
mWmService.mWindowsChanged = true;
// TODO: Should we also be setting layout needed here and other places?
}
}
//这里是比较器的实现
private final Comparator<WindowState> mWindowComparator =
(WindowState newWindow, WindowState existingWindow) -> {
final WindowToken token = WindowToken.this;
//检查需要对比的两个窗口是否属于当前WindowToken的集合,如果不是就会报错
if (newWindow.mToken != token) {
throw new IllegalArgumentException("newWindow=" + newWindow
+ " is not a child of token=" + token);
}
if (existingWindow.mToken != token) {
throw new IllegalArgumentException("existingWindow=" + existingWindow
+ " is not a child of token=" + token);
}
//比较器的核心算法,不过他外包了
return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
};
//核心算法的实现
protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
WindowState existingWindow) {
//其实就是比较哪个窗口的mBaseLayer更大
return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
}
这里addChild方法和上面一样就不再看了。
9、 应用窗口的排序:
WindowToken会有排序,那万一这里是他的子类ActivityRecord,那不就也会排序嘛?我们看到ActivityRecord中也有重写isFirstChildWindowGreaterThanSecond方法,所以需要对应用窗口再进行不同规则的排序。看到方法的实现:
protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
WindowState existingWindow) {
final int type1 = newWindow.mAttrs.type;
final int type2 = existingWindow.mAttrs.type;
//添加的窗口是BaseApplication,且被比较的不是BaseApplication的话,就将添加的窗口放在当前位置
if (type1 == TYPE_BASE_APPLICATION && type2 != TYPE_BASE_APPLICATION) {
return false;
//添加窗口不是BaseApplication,比较的窗口是的话,就继续向后比较
} else if (type1 != TYPE_BASE_APPLICATION && type2 == TYPE_BASE_APPLICATION) {
return true;
}
// 对于启动窗口的处理
if (type1 == TYPE_APPLICATION_STARTING && type2 != TYPE_APPLICATION_STARTING) {
return true;
} else if (type1 != TYPE_APPLICATION_STARTING && type2 == TYPE_APPLICATION_STARTING) {
return false;
}
return true;
}
总结就是基础窗口放在下面,启动窗口放在上面。这里的windowList的排序规则就是越靠后的窗口显示在越上层。
10、后话:
这里就是我总结的一些必备知识点,可能也会对Android源码有些误解读,如果大佬们发现了,也麻烦帮忙指正。最后也放一张这里比较复杂的几个类之间的关系图: