Android窗口系统第一篇---Window的类型与Z-Order确定

Android的窗口系统是UI架构很重要的一部分,数据结构比较多,细节比较多。本篇文章主要介绍窗口相关数据结构和抽象概念理解,关于[窗口部分的博客]计划如下。
1、窗口Z-Order的管理
2、应用程序和WMS的联系
3、窗口的添加,WindowState的创建
4、Token管理,AppToken
5、窗口大小的计算
6、启动窗口
7、窗口的切换和动画
8、多窗口,分屏、画中画,自由模式的实现思路
9、View的测量和布局
10、桌面启动流程
11、墙纸流程

本篇文章主要讨论窗口类型、坐标系统、Z-Order,Z-Order确定跟窗口类型有关系,先看窗口类型。

一、窗口类型

1.1、Window的类型

Android系统的Window有很多个,大体上来说,Framework定义了三种窗口类型;

系统Window
常见的系统Window有哪些呢?比如在手机电量低的时候,会有一个提示电量低的Window,我们输入文字的时候,会弹出输入法Window,还有搜索条Window,来电显示Window,Toast对应的Window,可以总结出来,系统Window是独立与我们的应用程序的,对于应用程序而言,我们理论上是无法创建系统Window,因为没有权限,这个权限只有系统进程有。

应用程序Window
所谓应用窗口指的就是该窗口对应一个Activity,因此,要创建应用窗口就必须在Activity中完成了。本节后面会分析Activity对应的Window的创建过程。

子Window
所谓的子Window,是说这个Window必须要有一个父窗体,比如PopWindow,Dialog是属于应用程序Window,这个比较特殊。

每一种窗口类型定义了一种对应的type
应用类型的窗口的type范围是1~99


应用类型的窗口

子窗口的type范围是1000~1999


子窗口

系统的窗口的type范围是2000以上 


i系统的窗口

系统窗口的type值>子窗口的type值>应用类型窗口的type值,一般来说,根据type值大小关系,可以推出系统窗口在子窗口的上面,子窗口在应用窗口的上面。

二、窗口布局和Z序

窗口的布局一般有两种,一种是平铺式的布局,一种是层叠式的布局

平铺窗口布局

平铺式的布局设计简单,现在还有一些终端系统仍然是单窗口的平铺布局,手机上采用的是层叠式布局,层叠式布局是一个三维的空间,将手机的水平方向作为X轴,竖直方向作为Y轴,还有一根垂直与屏幕从里朝外方向的虚拟的Z轴,所有窗口 (WindowState) 按照顺序排列在Z轴上,如下图。

层叠式布局

2.1、窗口主序的确定

WindowState是WMS中事实的窗口,而不是Window,WindowState是在WMS的addWindow方法中创建,包含了一个窗口的所有的属性,其中一个属性为mLayer,表示窗口在Z轴的位置,mLayer值越小,窗口越靠后,mLayer值越大,窗口越靠前,最前面的一个窗口就作为焦点窗口,可以接收触摸事件。因为窗口的切换,切换后的Z序(窗口的显示次序称为 Z 序)就可能不同,所以mLayer的值不是固定不变的。mLayer是通过WindowState的另一个成员变量mBaseLayer的值计算得到,mBaseLayer的值是固定不变的,只和窗口类型有关。mBaseLayer(称为主序)是WindowState的构造方法中赋值。

mBaseLayer = mPolicy.windowTypeToLayerLw(
                   attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER
                   + WindowManagerService.TYPE_LAYER_OFFSET;

等价与:mBaseLayer =窗口类型×10000+1000
windowTypeToLayerLw根据窗口的类型type,返回2开始的整数值

public int windowTypeToLayerLw(int type) {
       if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
           return 2;
       }
       switch (type) {
       case TYPE_WALLPAPER:
             return 2;
           // wallpaper is at the bottom, though the window manager may move it.
       case TYPE_PHONE:
           return 3;
      .....
    case TYPE_TOAST:
           // toasts and the plugged-in battery thing
           return 8;
        .....
       case TYPE_BOOT_PROGRESS:
           return 30;
       case TYPE_POINTER:
           // the (mouse) pointer layer
           return 31;
       }
       Log.e(TAG, "Unknown window type: " + type);
       return 2;

可以看到系统中的窗口种类比较多,case个数都到31了。比如应用程序的窗口是2,电话类型的窗口是3,Toast窗口是8...,因为系统中同类型的窗口比较多,所以对返回的整数值乘以10000(WindowManagerService.TYPE_LAYER_MULTIPLIER),在加上1000(WindowManagerService.TYPE_LAYER_OFFSET),相当把Z轴划分成了31个值域,不同类型的窗口的Z轴位置都是处于两个不相交的值域之中,互相不打扰。OK,通过mBaseLayer,我们知道了窗口是如何排序的,一个窗口有可能需要依附在一个父窗口上,作为一个子窗口,所以除了主序的概念外,还有子序。

2.2、窗口子序的确定

SubLayer(称为子序),SubLayer值是用来描述一个窗口是否属于另外一个窗口的子窗口,或者说SubLayer值是用来确定子窗口和父窗口之间的相对位置的。

SubLayer.png

一个Activity中有三个子窗口WindowState1、WindowState2、WindowState3,WindowState1WindowState2在窗口A的前面,WindowState3在A的后面,这几个兄弟窗口为什么可以这样排序呢,这就是mSubLayer的作用,子序越大,则相对其他兄弟窗口越靠前,反之,越靠后,如果为负数,就处在父窗口的后面,如窗口A中的WindowState3,子序是根据窗口类型调用subWindowTypeToLayerLw确定的,subWindowTypeToLayerLw同样是在Window的构造方法中调用的。

  mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
 public int subWindowTypeToLayerLw(int type) {
       switch (type) {
       case TYPE_APPLICATION_PANEL:
       case TYPE_APPLICATION_ATTACHED_DIALOG:
           return APPLICATION_PANEL_SUBLAYER;//返回值是1
       case TYPE_APPLICATION_MEDIA:
           return APPLICATION_MEDIA_SUBLAYER;//返回值是-2  
       case TYPE_APPLICATION_MEDIA_OVERLAY:
           return APPLICATION_MEDIA_OVERLAY_SUBLAYER;//返回值是-1  
       case TYPE_APPLICATION_SUB_PANEL:
           return APPLICATION_SUB_PANEL_SUBLAYER;//返回值是2 
       case TYPE_APPLICATION_ABOVE_SUB_PANEL:
           return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;//返回值是3  
       }
       Log.e(TAG, "Unknown sub-window type: " + type);
       return 0;
   }

相对与主序的确定,子序是非常简单了,但是,假设系统中有个应用程序现在有5个窗口,全部都是应用类型的窗口,按照上面的规则,计算出来的主序应该相同,如何排序? 这就需要WMS进行后期的调整。

2.3、窗口Z序的调整

当WindowState创建完成,并且被添加到WMS维持的数组里面后,就需要调用WindowLayersController的assignLayersLocked(windows),进行Z序的调整。

//参数windows是窗口列表
final void assignLayersLocked(WindowList windows) {
       if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows,
               new RuntimeException("here").fillInStackTrace());

       clear();
       int curBaseLayer = 0;
       int curLayer = 0;
       boolean anyLayerChanged = false;
      //遍历窗口列表,上面通过Z序的计算公式计算出来的Z序值保存在WindowState的变量mBaseLayer
       中,这个循环的意思是,遇到同类型的窗口,后一个窗口在前一个窗口的基础上偏移5。
       for (int i = 0, windowCount = windows.size(); i < windowCount; i++) {
           final WindowState w = windows.get(i);
           boolean layerChanged = false;

           int oldLayer = w.mLayer;
           if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) {
               curLayer += WINDOW_LAYER_MULTIPLIER;
           } else {
               curBaseLayer = curLayer = w.mBaseLayer;
           }
          // 更新该窗口的mAnimLayer,也就是动画显示时,该窗口的层级
           assignAnimLayer(w, curLayer);

           // TODO: Preserved old behavior of code here but not sure comparing
           // oldLayer to mAnimLayer and mLayer makes sense...though the
           // worst case would be unintentionalp layer reassignment.
           if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
               layerChanged = true;
               anyLayerChanged = true;
           }

     // 将当前应用窗口的最高显示层级记录在mHighestApplicationLayer中
           if (w.mAppToken != null) {
               mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
                       w.mWinAnimator.mAnimLayer);
           }
          //  对于分屏等相关的窗口,它们的显示层级需要再次处理
           collectSpecialWindows(w);

           if (layerChanged) {
               w.scheduleAnimationIfDimming();
           }
       }

    // 调整特殊窗口的层级
       adjustSpecialWindows();

       //TODO (multidisplay): Magnification is supported only for the default display.
       if (mService.mAccessibilityController != null && anyLayerChanged
               && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) {
           mService.mAccessibilityController.onWindowLayersChangedLocked();
       }

       if (DEBUG_LAYERS) logDebugLayers(windows);
   }

经过调整之后,同类型的窗口的Z序值就不同了。

3、窗口Z序的深入理解

1、Layer值为何乘以1万?

Layer的计算公式为:

   mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)  * WindowManagerService.TYPE_LAYER_MULTIPLIER   + WindowManagerService.TYPE_LAYER_OFFSET; 
等价与:mBaseLayer =窗口类型×10000+1000

其中TYPE_LAYER_MULTIPLIER和TYPE_LAYER_OFFSET在WMS的声明如下,

/** How much to multiply the policy's type layer, to reserve room
 * for multiple windows of the same type and Z-ordering adjustment
 * with TYPE_LAYER_OFFSET. 
  */
static final int TYPE_LAYER_MULTIPLIER = 10000;

/** Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above
  * or below others in the same layer. 
  */
static final int TYPE_LAYER_OFFSET = 1000;

mBaseLayer的值是在WindowState的构造中计算的,子窗口和一般窗口计算有所不同
if ((mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW)) {//如果是子窗口  
   // The multiplier here is to reserve space for multiple  
   // windows in the same type layer.  
    //使用依附窗口的类型 
   mBaseLayer = mPolicy.windowTypeToLayerLw(attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER  
           + WindowManagerService.TYPE_LAYER_OFFSET;  
   mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);//计算mSubLayer  
   ......  
} else {//非子窗口  
   // The multiplier here is to reserve space for multiple  
   // windows in the same type layer.  
   mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)  
           * WindowManagerService.TYPE_LAYER_MULTIPLIER  
           + WindowManagerService.TYPE_LAYER_OFFSET;  
   mSubLayer = 0;  
   ......  
} 

Layer值为什么要乘以10000万呢?PhoneWindowManager对象mPolicy的成员函数windowTypeToLayerLw的返回值并且不是一个窗口的最终的BaseLayer值,而是要将它的返回值乘以一个常量TYPE_LAYER_MULTIPLIER(10000),再加上另外一个常量TYPE_LAYER_OFFSET(1000)之后,才得到最终的BaseLayer值。这是因为相同类型的窗口的Z轴位置都是有着相同的值域,而不同类型的窗口的Z轴位置都是处于两个不相交的值域。例如,假设有两种不同类型的窗口,它们的Z轴位置的值域分别为[a, b]和[c, d],那么[a, b]和[c, d]的交集一定等于空。又由于每一种类型的窗口的数量是不确定的,因此,WindowManagerService服务就需要为每一种类型的窗口都预留一个范围足够大的值域,以便可以满足要求。

2、Z序调整为何加5而不加其他数字

Z序的调整是在WindowLayersController的assignLayersLocked方法实现的。所谓的调整就是遍历窗口列表,如果窗口的mBaseLayer和前一个相同、或者是输入法和壁纸窗口就加上常量值WINDOW_LAYER_MULTIPLIER。

private final void assignLayersLocked(WindowList windows) {  
   int N = windows.size();  
   int curBaseLayer = 0;  
   int curLayer = 0;  
   int i;  
  
   boolean anyLayerChanged = false;  
  
   for (i=0; i<N; i++) {  
       final WindowState w = windows.get(i);  
       final WindowStateAnimator winAnimator = w.mWinAnimator;  
       boolean layerChanged = false;  
       int oldLayer = w.mLayer;  
       if (w.mBaseLayer == curBaseLayer || w.mIsImWindow  || (i > 0 && w.mIsWallpaper)) {  
           curLayer += WINDOW_LAYER_MULTIPLIER;  
           w.mLayer = curLayer;  
       } else {  
           curBaseLayer = curLayer = w.mBaseLayer;  
           w.mLayer = curLayer;  
       }  
      ....... 
   }  
   .......
}  

WINDOW_LAYER_MULTIPLIER在WMS的声明如下

/** How much to increment the layer for each window, to reserve room
 * for effect surfaces between them.
 */
static final int WINDOW_LAYER_MULTIPLIER = 5;

意思为在每个窗口层之间预留空间。加上5之后,在同层的窗口中每隔一个窗口就留下4个空位, 如果加上1,按道理来说也行,为了搞清楚为什么加5,我给google维护窗口系统的工程师发了一封邮件。

google-mail.png

但是很遗憾,一直没有收到回信,但是应该可以确定,这里加1也是可以的,同样可以使得同类型的窗口的Z序不同,WINDOW_LAYER_MULTIPLIER这个值从Android2.3版本就是等于5,所以这里WINDOW_LAYER_MULTIPLIER为5并没有什么特殊的含义。

3、Layer值为何乘以1万以后为什么还要加上1000?

窗口在打开或者关闭的时候,为了不那么突兀,都会设置一个动画,

 /** Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above
    * or below others in the same layer. */
   static final int TYPE_LAYER_OFFSET = 1000;

public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame,
           int stackClip) {
      ....
       animation = anim;
 // 获得zorder类型,这个类型是通过Animaion.java的setZAdjustment设置的
       int zorder = anim.getZAdjustment();
       int adj = 0;
      // ZORDER_TOP则会+1000,ZORDER_BOTTOM则会-1000
       if (zorder == Animation.ZORDER_TOP) {
           adj = TYPE_LAYER_OFFSET;
       } else if (zorder == Animation.ZORDER_BOTTOM) {
           adj = -TYPE_LAYER_OFFSET;
       }

       if (animLayerAdjustment != adj) {
           animLayerAdjustment = adj;
           updateLayers();
       }
    ....
   }

所以Z序号设置1000的偏移量就是为了这个动画层级属性预留的空间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容