本篇基于Android Q代码,建议先看从一个Dialog的创建看Android的窗口机制(上篇)和从一个Dialog的创建看Android的窗口机制(下篇)大致了解窗口的添加机制再看本篇

本篇主要分析Android窗口的Z轴计算规则以及根据Z轴对WindowState的排列规则,从之前两篇关于Dialog的分析知道了窗口的添加最终会到WMS的addWindow方法中,窗口的Z轴计算就是在addWindow方法中进行的,Z轴越大越靠前

WMS.addWindow

public int addWindow(Session session, IWindow client, int seq,LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,Rect outContentInsets, Rect outStableInsets, Rect outOutsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,InsetsState outInsetsState) {......//每个窗口在WMS都会对应一个WindowState,描述了窗口相关信息final WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], seq, attrs, viewVisibility, session.mUid,session.mCanAddInternalSystemWindow);......//将窗口添加到WindowContainer的WindowList中,并进行排序//这里的WindowContainer有两种类型:AppWindowToken和WindowTokenwin.mToken.addWindow(win);......
}

addWindow这个方法在从一个Dialog的创建看Android的窗口机制(下篇)已经进行过详细分析,我们这篇主要来详细看下上面这两部分

WindowState

WindowState中用mBaseLayer描述窗口的Z轴数,这个值只会和窗口的类型有关,mSubLayer描述子窗口的Z轴数

WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,PowerManagerWrapper powerManagerWrapper) {super(service);//省略赋值代码......//如果是子窗口if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {//计算窗口mBaseLayer,传入的是父窗口mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;//计算窗口mSubLayer,传入的是当前窗口的类型mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);mIsChildWindow = true;//根据自定义的比较器sWindowSubLayerComparator对子窗口排序parentWindow.addChild(this, sWindowSubLayerComparator);....} else {//计算窗口mBaseLayermBaseLayer = mPolicy.getWindowLayerLw(this)* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;//非子窗口mSubLayer为0mSubLayer = 0;mIsChildWindow = false;.....}//如果是应用窗口并且mShowForAllUsers为trueif (mAppToken != null && mAppToken.mShowForAllUsers) {//添加FLAG_SHOW_WHEN_LOCKED,代表可以显示在锁屏上mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED;}mWinAnimator = new WindowStateAnimator(this);mWinAnimator.mAlpha = a.alpha;mRequestedWidth = 0;mRequestedHeight = 0;mLastRequestedWidth = 0;mLastRequestedHeight = 0;mLayer = 0;}

先看子窗口的分支

WindowManagerPolicy

计算mBaseLayer,有三十多种类型,根据不同类型返回不同的值,例如壁纸为1,Toast为8,屏保为14等等,很好理解,mBaseLayer的计算还是很简单的

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;   //2}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_SEARCH_BAR:case TYPE_VOICE_INTERACTION_STARTING:return  4;case TYPE_VOICE_INTERACTION:return  5;case TYPE_INPUT_CONSUMER:return  6;case TYPE_SYSTEM_DIALOG:return  7;case TYPE_TOAST:return  8;case TYPE_PRIORITY_PHONE:return  9;case TYPE_SYSTEM_ALERT:return  canAddInternalSystemWindow ? 13 : 10;case TYPE_APPLICATION_OVERLAY:return  12;case TYPE_DREAM:return  14;......default:Slog.e("WindowManager", "Unknown window type: " + type);return APPLICATION_LAYER;  //2}}

最后计算出来的mBaseLayer还会乘上一个乘数因子
TYPE_LAYER_MULTIPLIER等于10000,TYPE_LAYER_OFFSET等于1000,例如普通的应用Activity计算下来 mBaseLayer = 2 * 10000 + 1000
乘上这么大一个数是为了给同类型的窗口保留足够的空间以至于不会出现重复

mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;

接着看mSubLayer的计算,同样是根据不同子窗口的类型返回不同的值,比如APPLICATION_PANEL_SUBLAYER等于FIRST_SUB_WINDOW + 3,FIRST_SUB_WINDOW等于1000

mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);default int getSubWindowLayerFromTypeLw(int type) {switch (type) {case TYPE_APPLICATION_PANEL:case TYPE_APPLICATION_ATTACHED_DIALOG:return APPLICATION_PANEL_SUBLAYER; //1003case TYPE_APPLICATION_MEDIA:return APPLICATION_MEDIA_SUBLAYER; //-2case TYPE_APPLICATION_MEDIA_OVERLAY:return APPLICATION_MEDIA_OVERLAY_SUBLAYER; //-1case TYPE_APPLICATION_SUB_PANEL:return APPLICATION_SUB_PANEL_SUBLAYER; //2case TYPE_APPLICATION_ABOVE_SUB_PANEL:return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER; //3}Slog.e("WindowManager", "Unknown sub-window type: " + type);return 0;}

自定义排序规则

接着看子窗口的添加规则
parentWindow.addChild(this, sWindowSubLayerComparator),通过自定义的比较器对子窗口进行排序,哪两个窗口排序?当前添加的窗口会和WindowList中的所有窗口比较,这里的所有窗口指的是同一父窗口下的所以窗口

private static final Comparator<WindowState> sWindowSubLayerComparator =new Comparator<WindowState>() {@Overridepublic int compare(WindowState w1, WindowState w2) {final int layer1 = w1.mSubLayer;final int layer2 = w2.mSubLayer;if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {/*对两个窗口的计算出来的mSubLayer进行比较w1代表当前添加的窗口,w2代表已存在的窗口当前窗口的mSubLayer小于已存在的窗口的mSubLayer或者两者相等且都小于0则返回-1*/return -1;}return 1;};};

WindowContainer.addChild

WindowState并没有覆盖父类的addChild方法,mChildren是WindowList类型容器,继承自ArrayList

protected void addChild(E child, Comparator<E> comparator) {//如果getParent()不为空代表已经添加过了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());}//初始化positionToAdd为-1int positionToAdd = -1;if (comparator != null) {final int count = mChildren.size();for (int i = 0; i < count; i++) {//把当前窗口的mSubLayer与mChildren里的所有窗口比较,只要mChildren中//有一个窗口的mSubLayer大于当前窗口就可以结束比较了//因为mChildren里总是按照mSubLayer大小顺序排列的if (comparator.compare(child, mChildren.get(i)) < 0) {positionToAdd = i;break;}}}//positionToAdd等于-1说明当前添加的窗口的mSubLayer大于等于//所有mChildren里的窗口if (positionToAdd == -1) {//则直接添加到mChildren的尾部mChildren.add(child);} else {//否则将当前子窗口插入比较的那个窗口所在的位置mChildren.add(positionToAdd, child);}//回调onChildAdded(child);//这个方法最终会通过SurfaceControl的Transaction类的setLayer//方法,再通过JNI调用SurfaceFlinger的客户端代理SurfaceComposerClient//将窗口Z轴设置到SurfaceFlinger中去child.setParent(this);}

总结一下子窗口的Z轴计算和窗口排列规则:
Z轴计算:子窗口的mBaseLayer由父窗口的type决定,根据对应type获取一个值然后乘上一个因子1000再加上1000,常见的应用Activity的窗口就是21000,子窗口还有一个mSubLayer,这个值根据子窗口自己的类型来获取的
窗口排列:子窗口添加到WindowContainer中的WindowList规则是根据mSubLayer的大小进行排序,大于或等于会依次排列在WindowList尾部,小于则插入对应比较的窗口所在的位置,所以WindowList是按顺序从小到大排列的

再回到WindowState构造方法看下非子窗口的分支,非子窗口很简单,根据自己窗口的type计算mBaseLayer,并将mSubLayer赋值为0

WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,PowerManagerWrapper powerManagerWrapper) {super(service);//省略赋值代码......//如果是子窗口if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {//计算窗口mBaseLayer,传入的是父窗口mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;//计算窗口mSubLayer,传入的是当前窗口的类型mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);mIsChildWindow = true;//根据自定义的比较器sWindowSubLayerComparator对子窗口排序parentWindow.addChild(this, sWindowSubLayerComparator);....} else {//计算窗口mBaseLayermBaseLayer = mPolicy.getWindowLayerLw(this)* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;//非子窗口mSubLayer为0mSubLayer = 0;mIsChildWindow = false;.....}//如果是应用窗口并且mShowForAllUsers为trueif (mAppToken != null && mAppToken.mShowForAllUsers) {//添加FLAG_SHOW_WHEN_LOCKED,代表可以显示在锁屏上mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED;}mWinAnimator = new WindowStateAnimator(this);mWinAnimator.mAlpha = a.alpha;mRequestedWidth = 0;mRequestedHeight = 0;mLastRequestedWidth = 0;mLastRequestedHeight = 0;mLayer = 0;}

我们可以看到非子窗口并没有在WindowState构造方法中将自己添加到WindowList中,那在哪里添加的呢?我们回到WMS的addWindow方法

public int addWindow(Session session, IWindow client, int seq,LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,Rect outContentInsets, Rect outStableInsets, Rect outOutsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,InsetsState outInsetsState) {......//每个窗口在WMS都会对应一个WindowState,描述了窗口相关信息final WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], seq, attrs, viewVisibility, session.mUid,session.mCanAddInternalSystemWindow);......//将窗口添加到WindowContainer的WindowList中,并进行排序win.mToken.addWindow(win);......
}

win.mToken.addWindow(win)

mToken分为两种,应用类型窗口对应AppWindowToken,系统类型窗口对应WindowToken,AppWindowToken是WindowToken的子类,AppWindowToken的addWindow方法其实也会调到父类WindowToken的addWindow方法,我们就直接看WindowToken的addWindow方法

 void addWindow(final WindowState win) {if (DEBUG_FOCUS) Slog.d(TAG_WM,"addWindow: win=" + win + " Callers=" + Debug.getCallers(5));//此窗口是子窗口直接return,子窗口已经在WindowState构造方法中添加了if (win.isChildWindow()) {return;}//此窗口没有向WindowList添加过if (!mChildren.contains(win)) {//通过自定义比较器mWindowComparator对窗口进行排序addChild(win, mWindowComparator);//窗口添加之后代表窗口已经发生变化mWmService.mWindowsChanged = true;}}

非子窗口的添加和子窗口添加最终流程都一样,都会调到WindowContainer的addChild方法,只是比较器不同对应的窗口排序规则不同

mWindowComparator

private final Comparator<WindowState> mWindowComparator =(WindowState newWindow, WindowState existingWindow) -> {final WindowToken token = WindowToken.this;//窗口mToken和token类型一致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;};

isFirstChildWindowGreaterThanSecond

这个方法子类AppWindowToken进行覆写了,应用窗口和系统窗口的排序规则不一样,我们先看简单的,系统窗口的排序规则:直接对两个系统窗口的mBaseLayer进行比较,和子窗口的排序方式类似,mBaseLayer大的排在WindowList尾部

protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,WindowState existingWindow) {return newWindow.mBaseLayer >= existingWindow.mBaseLayer;}

应用窗口排序规则:

 @Overrideprotected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,WindowState existingWindow) {final int type1 = newWindow.mAttrs.type;final int type2 = existingWindow.mAttrs.type;//如果添加的窗口是最基本的窗口类型,并且WindowList里的窗口不是此类型//则return false,就是-1,按照WindowContainer的排序规则,返回-1代表//需要将此添加的窗口插入到比较的窗口所在位置if (type1 == TYPE_BASE_APPLICATION && type2 != TYPE_BASE_APPLICATION) {return false;//反之则返回true,即将新添加的窗口按顺序添加在WindowList的尾部} else if (type1 != TYPE_BASE_APPLICATION && type2 == TYPE_BASE_APPLICATION) {return true;}// 如果添加的窗口是启动窗口类型,并且WindowList里的窗口不是此类型//则返回true,即将新添加的窗口按顺序添加在WindowList的尾部if (type1 == TYPE_APPLICATION_STARTING && type2 != TYPE_APPLICATION_STARTING) {return true;//反之则返回false,即将新添加的窗口插入到比较的窗口的位置} else if (type1 != TYPE_APPLICATION_STARTING && type2 == TYPE_APPLICATION_STARTING) {return false;}//其他普通的应用类型窗口都返回true,即按顺序添加到//WindowList尾部return true;}

需要注意的是以上的排序指的是同一个token下的一组WindowState的排序,例如一个AppWindowToken下可能会有一组子窗口和一组应用类型窗口

WindowList就是一个普通的ArrayList,里面的窗口越靠尾部代表层级越高,越靠前代表层级越低,最终窗口的合成就是按照WindowList中的顺序,屏幕最上层的窗口在WindowList的尾部,屏幕最底层窗口在WindowList头部,所以窗口比较器中返回false代表此窗口Z轴小于与之比较的窗口的Z轴,就应该插入在它前面,返回true代表Z轴最高应该放在尾部
举个例子【1,3,5,7】,如果来一个数2,按照比较器规则,在它和3比较时返回false则应该插入3的前面,来个数字10,遍历所以数字都是返回true,则应该添加到尾部,我们以上分析的Z轴顺序只是在WMS一侧的,SurfaceFlinger一侧的窗口顺序和WMS对应的,通过SurfaceControl的Transaction类的setLayer方法,再通过JNI调用SurfaceFlinger的客户端代理SurfaceComposerClient将窗口Z轴设置到SurfaceFlinger中去,我们就不去细看了

最终WindowState的Z轴排序规则就是:

1.对三种不同类型的窗口都会自定义一个比较器用来排序

2.系统类型窗口比较器规则按照mBaseLayer从小到大排列

3.如果是应用类型窗口则对于TYPE_BASE_APPLICATION窗口总是放在最底下,对于TYPE_APPLICATION_STARTING窗口总是放在最上面,其他则按顺序添加到WindowList尾部,例如Activity和Dialog就按顺序添加

4.对于子窗口因为mBaseLayer都一样,则需要再依靠mSubLayer进行排序

Android窗口Z轴计算以及WindowState排列规则相关推荐

  1. Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8570428 通过前面几篇文章的学习,我们知道了 ...

  2. 计算窗口Z轴位置的过程分析

    在Android系统中,无论是普通的Activity窗口,还是特殊的输入法窗口和壁纸窗口,它们都是被WindowManagerService服务组织在一个窗口堆栈中的,其中,Z轴位置较大的窗口排列在Z ...

  3. Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析

    在Android系统中,Activity窗口的大小是由WindowManagerService服务来计算的.WindowManagerService服务会根据屏幕及其装饰区的大小来决定Activity ...

  4. 依据imu姿态角计算z轴倾角_1. 姿态的表示方法

    刚体的姿态(attitude)有很多种表示方法,关于这个话题有一篇十分出名的综述[1],也是这篇文章的主要资料来源.这篇文章从二维旋转开始,会讨论旋转矢量.旋转矩阵.四元数.欧拉角等旋转的表示方法.在 ...

  5. android 动画x轴旋转,android – 动画在视图之间转换,在z轴上旋转...

    我正在尝试创建一个动画来在两个视图之间转换,这两个视图都是ExpandableListViews.我想要的外观如下图所示. 我尝试使用shrink_to_middle和grow_from_middle ...

  6. Android X轴Y轴Z轴旋转

    Android中并没有提供直接做3D翻转的动画,所以关于3D翻转的动画效果需要我们自己实现,一个简单的办法就是重写Animation.这里只是以Y轴旋转进行下说明,至于X轴.Z轴和Y轴的原理是一样的. ...

  7. 【原创】终结版 窗口前置 方案 最前面 Z 轴 窗体 最前面

    要真正的理解和解决这个问题,确实不容易.当我花了好几天时间才搞明白之后(每天都会纠结新的问题),我真想写一本书. 首先说明下让窗口前置的方法非常多,但现有(2011年1月26号)网上(公开中文论坛资料 ...

  8. Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析

    在Android系统中,壁纸窗口和输入法窗口一样,都是一种特殊类型的窗口,而且它们都是喜欢和一个普通的Activity窗口缠绵在一起.大家可以充分地想象这样的一个3W场景:输入法窗口在上面,壁纸窗口在 ...

  9. Android窗口管理服务WindowManagerService切换Activity窗口(App Transition)的过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8596449 在Android系统中,同一时刻只 ...

最新文章

  1. 2021暑假实习-SSM超市积分管理系统-day08笔记
  2. java的URLConnection
  3. dubbo服务降级与限流
  4. key php 转小写_php字符串替换以及大写字母转小写字母
  5. C#串口通信工作笔记0001---上位机开发_嵌入式_串口助手_收发数据开发
  6. 教学平台服务器安装环境说明
  7. Oracle使用Shell脚本导出Excel表格
  8. 入行必看、经验不足必看:财务会计需要会的那些软件工具
  9. 【辅助开发】游戏辅助开发全流程-golang
  10. IBM-P55A小型机内存故障
  11. Windows 默认以管理员身份运行批处理bat文件
  12. mysql 1548_MySQL创建函数由于Error 1548失败
  13. 开始尝试淘宝直通车推广
  14. VS2019下编译与配置GSL2.7【Release x64版】
  15. hihoCoder1044
  16. TX2安装pytorch+TensorRT+yolov5实现实时检测
  17. 车间调度-灰狼算法的应用:以算例MK01为例
  18. AJPFX:什么是外汇交易
  19. 安装JDK,配置环境变量,测试JDK是否安装完成
  20. idm 使用本地hosts 破解步骤

热门文章

  1. LWN:内核该如何处理argc为0的情况?
  2. 哈利波特手游再次道歉
  3. Vue 入门学习视频教程
  4. HTML5+CSS3+JavaScript 实现按键令小女孩移动,改变动画效果
  5. 只要单片机具有真正唯一ID,就可以让加密坚不可摧(转)
  6. trunc函数的用法
  7. 【免费超简单】班级专用抽奖点名系统支持语音报名等老师再也不用担心上课没趣味了
  8. 网页配色工具(精品)
  9. php explode 效率,PHP字符分割explode,split,preg_split性能比较
  10. 最强蜗牛击败毁灭机器人_【最强蜗牛攻略】恶魔所有阶段攻略(详细教程)