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以上
系统窗口的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值是用来确定子窗口和父窗口之间的相对位置的。
一个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维护窗口系统的工程师发了一封邮件。
但是很遗憾,一直没有收到回信,但是应该可以确定,这里加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的偏移量就是为了这个动画层级属性预留的空间。
作者:LooperJing
链接:https://www.jianshu.com/p/f3ec3f6e0f6b
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
Android窗口系统第一篇---Window的类型与Z-Order确定的更多相关文章
- Android窗口系统第二篇---Window的添加过程
以前写过客户端Window的创建过程,大概是这样子的.我们一开始从Thread中的handleLaunchActivity方法开始分析,首先加载Activity的字节码文件,利用反射的方式创建一个Ac ...
- Android窗口系统第三篇---WindowManagerService中窗口的组织方式
Android窗口系统第一篇—Window的类型与Z-Order确定 Android窗口系统第二篇—Window的添加过程 上面文章梳理了一个窗口的添加过程,系统中有很多应用,每个应用有多个Activ ...
- 第六篇 ANDROID窗口系统机制之显示机制
第六篇 ANDROID窗口系统机制之显示机制 ANDROID的显示系统是整个框架中最复杂的系统之一,涉及包括窗口管理服务.VIEW视图系统.SurfaceFlinger本地服务.硬件加速等.窗口管理服 ...
- Android开源项目第一篇——个性化控件(View)篇
本文为那些不错的Android开源项目第一篇——个性化控件(View)篇,主要介绍Android上那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Ga ...
- Android基础学习第一篇—Project目录结构
写在前面的话: 1. 最近在自学Android,也是边看书边写一些Demo,由于知识点越来越多,脑子越来越记不清楚,所以打算写成读书笔记,供以后查看,也算是把自己学到所理解的东西写出来,献丑,如有不对 ...
- “MVC+Nhibernate+Jquery-EasyUI”信息发布系统 第一篇
一.第一篇内容中必须得简单的介绍一下MVC(在大学时用的是WebForm,拖控件感觉很爽,但是工作后,技术总监让一定要放弃这种 想法!) 1.MVC是什么?:(1) ASP.NET MVC是微软官方提 ...
- Android JNI入门第二篇——Java参数类型与本地参数类型对照
前面一篇通过简单的例子介绍了android中JNI的使用.这一篇从基础上了解一些Java参数类型与本地参数类型区别. 1) java中的返回值void和JNI中的void是完全对应的哦! ...
- Android JNI入门第一篇——HelloJni
android支持使用NDK开发C程序,关于配置NDK环境问题应该不用再赘述了,这个网上有很多,这里通过一篇实例来讲述简单的JNI开发,大家可以参考这篇文章(Get Your Eclipse-Inte ...
- Android学习笔记(第一篇)编写第一个程序Hello World+Activity
PS:终于开始正式的搞Android了...无人带的一介菜鸟,我还是自己默默的努力吧... 学习内容: 1.编写第一个Hello World程序.. 学习Android,那么就需要有一个编译器来集 ...
随机推荐
- AutoCAD如何移动零件和缩放零件图
如下图所示,我想要把这个零件放大并移动到图纸的中央,先全部选中这个零件,方法是在左上角点一下,然后拖出一个矩形包围整个零件 然后点击右侧的缩放命令,底部的命令栏变成指定基点的时候,在这个图纸的右上 ...
- vue修改数组元素方法
示例代码 <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF- ...
- Shell脚本值:运算符
算术运算符 原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用. expr 是一款表达式计算工具,使用它能完成表达式的求值操作. 例如:实现两个 ...
- 浅谈对TDD的看法
程序猿对TDD这个词一定不陌生.近几年比較火.英文全称Test-Driven Development,測试驱动开发. 它要求在编写某个功能的代码之前先编写測试代码,然后仅仅编写使測试通过的功能代码,通 ...
- Javascript模式(三) 策略模式
var data = { "username" : "zhangsan", "password" : "12345690" ...
- ARM内核和架构
转:深入浅谈,CPU设计原理 CPU的内部架构和工作原理 推荐一本书:编码的奥秘 一.ARM内核和架构 ARM产品越来越丰富,命名也越来越多.很多朋友提问: ARM内核和架构都是什么 ...
- 【Python】Selenium元素定位错误之解决办法
当使用class定位元素时发现报错: 错误信息:selenium.common.exceptions.InvalidSelectorException: Message: Compound class ...
- eclipse spring4 ehache2.10 整合
http://blog.csdn.net/tonytfjing/article/details/39251507 http://my.oschina.net/duoduo3369/blog/17392 ...
- 【Android】第三方库使用的问题集
Google/百度地图Key的获取 百度地图UnsatisfiedLinkError错误 async-http-client中的FATAL EXCEPTION Google/百度地图Key的获取 无论 ...
- hadoop 出现FATAL conf.Configuration: error parsing conf file,异常
FATAL conf.Configuration: error parsing conf file: com.sun.org.apache.xerces.internal.impl.io.Malfor ...