java怎么做分层窗口,WindowManagerService架构剖析之窗口分组与分层

WindowManagerService工作方式

一、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是子窗口类型吗?

3528255475a2

Dialog-addView.jpg

关键的点有:

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确定)。

3528255475a2

Window分组.jpg

2.3 Window 层级

前面铺垫了这么多,又是分组又是token比较,其实已经讲了一些mBaseLayer相关的知识了,这时候Windiw分层已经呼之欲出了。所谓分层,就是显示的时候有上下次序,上面的window会覆盖下面的window,我们需要知道哪些window应该放在上面,依据是什么?这是本小节探讨的问题。

window之所以要分层级,因为我们看到的手机直观上是二维的,但是实际上是一个三维的显示过程,就像一个三维的坐标系一样。

3528255475a2

Window层级图 (1).jpg

还是要讲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层级越在上面。下面看看详细的计算过程。

3528255475a2

z-order计算调用过程.jpg

真正的计算过程代码如下:

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的次序。

接下来准备分析一下窗口大小计算、窗口动画等等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值