WindowManagerService工作方式
《WindowManagerService架构剖析之addWindow流程》
《WindowManagerService架构剖析之窗口分组与分层》
《WindowManagerService架构剖析之token分析》
一、Window介绍
Window定义了Android中顶层的显示系统和行为规则,Android中的View都是以Window为模板,都是附在WIndow上,从Activity中setContentView(...)中就可以看出Window以及PhoneWindow(Window的子类)就是Android显示层级的最顶层(这里说的最顶层不是显示在最上层,只是说这个Window是整个UI显示的基础)
2.1 Window 类型
每个Window都有type,就是窗口类型,我们在WMS中addWindow的时候会根据当前的窗口类型采取相应的策略。通常而言,窗口分为3类:窗口类型定义在WindowManager.java
2.1.1 系统窗口类型
public static final int FIRST_SYSTEM_WINDOW = 2000;
//显示状态栏,被放置在屏幕最上方,其他的window都在它的下方
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
//searchbar的window,只能有一个。
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
//接电话的界面的window
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
//系统警告类的window,例如一些电量警告等等
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
//屏保窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//toast窗口
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16;
public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
public static final int TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22;
public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;
public static final int TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW+34;
public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;
public static final int TYPE_SCREENSHOT = FIRST_SYSTEM_WINDOW + 36;
public static final int TYPE_PRESENTATION = FIRST_SYSTEM_WINDOW + 37;
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
public static final int LAST_SYSTEM_WINDOW = 2999;
系统窗口类型在2000 ~ 2999,随便找一个系统窗口类型赋值的地方,如下:
BaseErrorDialog.java
class BaseErrorDialog extends AlertDialog {
public BaseErrorDialog(Context context) {
super(context, com.android.internal.R.style.Theme_Dialog_AppError);
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
//......
getWindow().setAttributes(attrs);
}
}
2.1.2.应用窗口类型
WindowManager.java
//作为普通应用程序窗口类型的开始
public static final int FIRST_APPLICATION_WINDOW = 1;
//作为所有应用程序的基础窗口
public static final int TYPE_BASE_APPLICATION = 1;
//一个普通的应用程序窗口,当前的token一定要设置成Activity的token,表明当前的window是有归属的
public static final int TYPE_APPLICATION = 2;
//特殊的应用程序窗口,用于程序启动的时候显示,当程序可以显示window之前使用这个window来显示一些东西
public static final int TYPE_APPLICATION_STARTING = 3;
//在应用程序显示之前,使用此标识来表明windowManager会等待这个window绘制完毕
public static final int TYPE_DRAWN_APPLICATION = 4;
//应用程序窗口类型的结束
public static final int LAST_APPLICATION_WINDOW = 99;
应用窗口类型在1~99找到应用窗口类型赋值的地方:根据上一篇文章对应用窗口启动过程的分析来看,如下:
ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
//......
r = performResumeActivity(token, clearHide, reason);
//......
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
//......
wm.addView(decor, l);
//......
}
2.1.3 子窗口类型
WindowManager.java
public static final int FIRST_SUB_WINDOW = 1000;
//一个应用程序顶部的panel,显示在依附的window上面
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
//显示media的window,显示在依附的window下面
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
//一个子panel,显示在依附的window上面,并且也显示在任何其他TYPE_APPLICATION_PANEL类型的window上面
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
public static final int LAST_SUB_WINDOW = 1999;
子窗口类型在1000~1999,通常所说的popupwindow就是子窗口,看一下赋值的例子。
PopupWindow.java
public class PopupWindow {
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
//......
p.type = mWindowLayoutType;
//......
return p;
}
private void invokePopup(WindowManager.LayoutParams p) {
//......
mWindowManager.addView(decorView, p);
//......
}
}
问题:Dialog是子窗口吗?2.2 Window 分组会分析这个问题。
小结:
1.type越小,越在上面,但是window的层级不单单是这个决定的,下面会详细分析。
2.type只能在这个范围之内,不在此范围,报错。
PhoneWindowManager.java
public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
int type = attrs.type;
if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW)
|| (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW)
|| (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) {
return WindowManagerGlobal.ADD_INVALID_TYPE;
}
//......
}
2.2 Window 分组
学习Window分组之前,建议先看一下《WindowManagerService架构剖析之token分析》https://www.jianshu.com/p/23bce4f5f8ea 这样理解起来容易一些。
这篇文章分析了一个道理:token,AMS-ActivityRecord中appToken,Window中mAppToken,WMS中的windowToken都代表同一个token,这个token就是标识window是否属于同一分组。
为什么这么说?光说不行,我们用源码分析一下就知道了。还是要扯到WMS中的addWindow方法,这个方法太重要了,这些天看这个代码,每次都有新的体会,每次理解都深刻一点。这次我们剔掉不重要的代码,只看关键部分。
WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
//......
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
//......
win.mToken.addWindow(win);
//......
}
WindowState就是windowManager中的窗口,一个WindowState表示一个window,一个WindowState持有一个WindowToken对象(mToken),这个WindowToken对象就是AMS中传入的appToken,但是这个addWindow是什么意思?
WindowToken.java
protected final WindowList mChildren = new WindowList(); //step1:
void addWindow(final WindowState win) {
if (win.isChildWindow()) { //step2:
return;
}
if (!mChildren.contains(win)) { //step3:
addChild(win, mWindowComparator);
mService.mWindowsChanged = true;
}
}
private final Comparator mWindowComparator =
(WindowState newWindow, WindowState existingWindow) -> {
final WindowToken token = WindowToken.this;
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;
};
1.mChildren列表就是当前window存储的子window列表
2.如果当前创建的window已经是子window,那么表示已经添加过了,不需要重复添加,直接return
3.如果当前window的存储的子window列表中没有child,则addChild
4.WindowToken 继承 WindowContainer
5.比较的原则是什么:比较两个window的mBaseLayer,至于什么是mBaseLayer,2.3 Window层级 会介绍。
6.mWindowComparator里面也能看出来,如果newWindow和existingWindow的token不一样,是不能比较的,直接throw异常了。
WindowContainer.java
protected void addChild(E child, Comparator comparator) {
if (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());
}
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 {
mChildren.add(positionToAdd, child);
}
// Set the parent after we've actually added a child in case a subclass depends on this.
child.setParent(this);
}
这段代码十分简单,找到当前child在mChildren列表中的位置(排序算法),然后将child插入相应的位置,保证插入child之后mChildren也是按序排列的。
当前window和child window列表都持有同一个token,这个token标识当前window同一分组。
现在可以分析2.1小节提出的问题了,Dialog是子窗口类型吗?
关键的点有:
1.getAttributes
Window.java
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
public final WindowManager.LayoutParams getAttributes() {
return mWindowAttributes;
}
WindowManager.java
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
有点意思了,当前的Dialog的window type是TYPE_APPLICATION,上面的窗口类型介绍了这是应用程序的窗口类型。说明这个Dialog是属于应用程序窗口的一种。
2.adjustLayoutParamsForSubWindow
Window.java
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
CharSequence curTitle = wp.getTitle();
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
if (wp.token == null) {
View decor = peekDecorView();
if (decor != null) {
wp.token = decor.getWindowToken();
}
}
//......
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
//......
} else {
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
//......
}
//......
if (mHardwareAccelerated ||
(mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
wp.flags |= FLAG_HARDWARE_ACCELERATED;
}
}
这个函数别看很长,但是对我们而言,有用的只有几行:
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
说明这个是直接使用mContainer.mAppToken,而这个mContainer,就是Dialog基于的Activity,这个明确了。
小结:
1.Dialog不是子窗口,是应用窗口的一种。
2.Dialog和它基于的Activity公用同一个token,说明它们是属于同一组。
PopupWindow就是名副其实的子窗口类型的,这里暂且不展开讲解,PopupWindow的token获取和Dialog还有不同,大家感兴趣可以去看下。(根据PopupWindow的锚点Window确定)。
2.3 Window 层级
前面铺垫了这么多,又是分组又是token比较,其实已经讲了一些mBaseLayer相关的知识了,这时候Windiw分层已经呼之欲出了。所谓分层,就是显示的时候有上下次序,上面的window会覆盖下面的window,我们需要知道哪些window应该放在上面,依据是什么?这是本小节探讨的问题。
window之所以要分层级,因为我们看到的手机直观上是二维的,但是实际上是一个三维的显示过程,就像一个三维的坐标系一样。
还是要讲WMS-addWindow,这个地方太重要了,添加窗口的时候就应该确定好了窗口的层级,显示的时候才可以根据当前的层级来确定window应该在哪一层显示。
前面我们讨论过WindowState对象对应Window,那么WindowState中肯定有标识当前window层级的变量和计算当前window层级的方法。WMS-addWindow中构造了一个WindowState对象。
WindowState.java
final int mBaseLayer;
final int mSubLayer;
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
//......
mToken = token;
//......
mWindowId = new WindowId(this);
//......
if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
//......
mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
mIsChildWindow = true;
parentWindow.addChild(this, sWindowSubLayerComparator);
//......
} else {
//......
mBaseLayer = mPolicy.getWindowLayerLw(this)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
mSubLayer = 0;
mIsChildWindow = false;
mLayoutAttached = false;
//......
}
//......
}
这段我们我们可以抽取出下面的信息:
1.WindowState构造的时候初始化当前的mBaseLayer和mSubLayer,这两个参数应该是决定z-order的两个因素,单还有没有其他的因素,暂时不得而知。
2.mBaseLayer在window策略中定义了决定其计算的函数,mSubLayer只有当前window是子窗口的时候才会计算,否则直接初始化0
PhoneWindowManager实现WindowManagerPolicy接口。
WindowManagerPolicy.java
default int getWindowLayerLw(WindowState win) {
return getWindowLayerFromTypeLw(win.getBaseType(), win.canAddInternalSystemWindow());
}
default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow) {
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
return APPLICATION_LAYER;
}
switch (type) {
case TYPE_WALLPAPER:
return 1;
case TYPE_PRESENTATION:
case TYPE_PRIVATE_PRESENTATION:
return APPLICATION_LAYER;
case TYPE_DOCK_DIVIDER:
return APPLICATION_LAYER;
case TYPE_QS_DIALOG:
return APPLICATION_LAYER;
case TYPE_PHONE:
return 3;
//......
case TYPE_BOOT_PROGRESS:
return 32;
case TYPE_POINTER:
return 33;
default:
return APPLICATION_LAYER;
}
}
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;
}
return 0;
}
baseLayer这儿实际上就是映射windowType到相应的到1~33区间,这个baseLayer会在后续计算真正window层级的时候用到。可见WindowState构造函数中只是粗略确定当前的baseLayer和subLayer,后面肯定还会根据这两个基础参数计算出最终的层级。
1.mBaseLayer是基础序,当前窗口类型确定的情况下,基础序是不变的。依附与同一个parent 窗口的 子窗口的mBaseLayer是相同的。
2.mSubLayer就是比较相同分组下的子窗口的序,简称子序。从代码上来看,mSubLayer的区间是-2~3
计算的公式是:
3.windowType越大,z-order不一定最大,因为windowType在计算过程中被重新映射了,上面就是映射的过程。
mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
static final int TYPE_LAYER_MULTIPLIER = 10000;
static final int TYPE_LAYER_OFFSET = 1000;
从这个计算公式来讲,只要parentWindow类型不同,那么mBaseLayer决定的大区间肯定是不同的,保证了每个windowType决定的mBaseLayer都会有相当大的缓冲区间,切不会出现不同windowType相等的情况。
接下来还有两个最终的步骤:
WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
//......
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
//......
win.mToken.addWindow(win);
//......
displayContent.assignWindowLayers(false /* setLayoutNeeded */);
//......
}
1.win.mToken.addWindow(win);
这个函数其实在2.2 Window分组的时候已经详细分析过,这个方法的主要作用就是讲当前的放入有序windowList数组中正确的位置。根据token找到归属的window列表,就是将window分组完成。
2.displayContent.assignWindowLayers(false /* setLayoutNeeded */);
这个方法非常关键,这是计算z-order值的过程,这个方法执行完成之后,z-order最终确定,z-order越大,表明window层级越在上面。下面看看详细的计算过程。
真正的计算过程代码如下:
WindowLayersController.java
private final Consumer mAssignWindowLayersConsumer = w -> {
boolean layerChanged = false;
int oldLayer = w.mLayer;
//step1:
if (w.mBaseLayer == mCurBaseLayer) {
mCurLayer += WINDOW_LAYER_MULTIPLIER;
} else {
mCurBaseLayer = mCurLayer = w.mBaseLayer;
}
//step2:
assignAnimLayer(w, mCurLayer);
//step3:
if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
layerChanged = true;
mAnyLayerChanged = true;
}
//step4:
if (w.mAppToken != null) {
mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
w.mWinAnimator.mAnimLayer);
}
if (mImeTarget != null && w.mBaseLayer == mImeTarget.mBaseLayer) {
mHighestLayerInImeTargetBaseLayer = Math.max(mHighestLayerInImeTargetBaseLayer,
w.mWinAnimator.mAnimLayer);
}
if (w.getAppToken() != null && StackId.isResizeableByDockedStack(w.getStackId())) {
mHighestDockedAffectedLayer = Math.max(mHighestDockedAffectedLayer,
w.mWinAnimator.mAnimLayer);
}
//step5:
collectSpecialWindows(w);
if (layerChanged) {
w.scheduleAnimationIfDimming();
}
};
这个是使用了java8推出的函数式编程:java.util.function.Consumer接口。注册的接口在合适的时机accept(...)一下,触发一下要执行的函数,这种写法符合lambda规则,代码看上去比较简洁。
step1:如果当前window和之前的window是同一类型的,为了以示区分,后一个窗口+5,偏移一下,标识区别。如果不是同一类型,那么更新一下mCurBaseLayer和mCurLayer
step2:更新动画层的layer,动画显示的时候,该窗口的层级会有变化。
step3:当前的层级有变化或者动画层级有变化,置位标识值,后面会用到。
step4:同时更新当前应用最高层级、IME最高层级、常驻栏最高层级
step5:如果当前的window是特殊窗口,将他们放入特定的数组中,这儿没有更新layer
1.这儿为什么没有遍历windowList?
整个accept(...)回调就在windowList的遍历中,此回调值计算单个window的最终layer
2.更新之后的layer,会通过setLayer将计算好的z-order值更新到SurfaceFlinger中,进而确定Surface的次序。
接下来准备分析一下窗口大小计算、窗口动画等等。