Window

https://www.cnblogs.com/huan89/p/14111360.html

从来都没什么Window,有的只是一个个View树,每个窗口(activity/dialog/popupWindow)都是一个view树(代码中都是叫addView,直到WMS中才叫addWindow),在需要显示时被添加进WMS中,最后通过surfalceFlinger合成后渲染到屏幕中。

每个窗口都对应一个Token,一个应用只对应一个session。

从Window视角看ActivityStart

Activity

简述:首先回顾一下,activity是怎么显示出来的:

  1. 为activity创建PhoneWindow和WindowManager(WindowManagerImpl)对象

在handleLaunchActivity()被回调的时候,调用WindowManagerGlobal.initialize();初始化WindoWindowManagerGlobal,之后 Application app = r.packageInfo.makeApplication(false, mInstrumentation);创建Application(如果还没有创建过Application),然后调用activity.attach(),这里面 mWindow = new PhoneWindow(this, window, activityConfigCallback);初始化PhoneWindow并给它设置WindowManager

1
2
3
4
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

Window.setWindowManager()中通过WMS创建了WindowManagerImpl(其实就是个一百多行代码的壳)。

从这里可以看到是利用系统服务的windowManager来创建新的windowManagerImpl,因而这个应用所有的WindowManagerImpl都是同个内核windowManager,而创建出来的仅仅是包了个壳。

  1. setContentView实际上掉的是getWindow()(也就是上面的PhoneWindow)的setContentView(),其中调用PhoneWindow.installDecor(),
  • 首先看decorView创建了没有,没有的话创建DecorView
  • 把布局加载到DecorView中(LayoutInflater加载预设模板布局,见下)

DecorView是在PhoneWindow中预设好的一个布局,这个布局长这样:

decorView

他是一个垂直排列的布局,上面是ActionBar,下面是ContentView,他是一个FrameLayout。我们的Activity布局就加载到ContentView里进行显示。所以Decorview是Activity布局最顶层的viewGroup。

// DecorView创建完成了,但还缺少了最重要的一步:把DecorView作为window添加到屏幕上。

  1. 在handleResumeActivity中,执行了最后的 wm.addView(mDecor, getWindow().getAttributes());

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
    String reason) {
    // 调用Activity的onResume方法
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ...
    // 让decorView显示到屏幕上
    if (r.activity.mVisibleFromClient) {
    r.activity.makeVisible();
    }

    void makeVisible() {
    if (!mWindowAdded) {
    ViewManager wm = getWindowManager();
    wm.addView(mDecor, getWindow().getAttributes());
    mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
    }

    直接调用WindowManagerImpl的addView方法来吧decorView添加到屏幕上,至此,我们的Activity界面就会显示在屏幕上了。

    进一步需要看WindowManagerImpl.addView之后是怎么将View(即Window)添加入屏幕的

    1. wm.addView(mDecor, getWindow().getAttributes());其中wm是WindowManagerImpl实例,WindowManagerImpl.addView其实是通过桥接,调用WindowManagerGlobal的全局单例的方法WindowManagerGlobal.addView,该方法中会新建一个ViewRootImpl,然后将入参的decorView、新建的ViewRootImp等加入自身维护的mViews、mRoots列表中,同时将DecorView注入ViewRootImplroot.setView(view, wparams, panelParentView)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    WindowMangerGlobal.clss
    维护着WMS实例sWindowManagerService和以下列表

    //应用所有的decorView
    private final ArrayList<View> mViews = new ArrayList<View>();
    //应用所有的ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    //应用所有的WindowManager.LayoutParams
    private final ArrayList<WindowManager.LayoutParams> mParams =
    new ArrayList<WindowManager.LayoutParams>();

    public void addView(View view, ViewGroup.LayoutParams params,
    Display display, Window parentWindow) {
    //... 主要是校验参数和调整子窗口的参数
    synchronized (mLock) {
    ...
    // 这里新建了一个viewRootImpl,并设置参数
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);

    // 添加到windowManagerGlobal的三个重要list中,每一个window所对应的这三个对象都会保存在这里,之后对window的一些操作就可以直接来这里取对象了。当window被删除的时候,这些对象也会被从list中移除。
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    // 最后通过viewRootImpl来添加window
    try {
    root.setView(view, wparams, panelParentView);
    }
    ...
    }
    }

​ 5. ViewRootImpl.setView()将调用到

1
2
3
4
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

这个mWindowSession来自于构造器的入参,是由WindowManagerGlobal.getWindowSession中来的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
...
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
...
});
}
...
}
return sWindowSession;
}
}

可以看出,这个session是一个单例,也就是整个应用的所有viewRootImpl的windowSession都是同一个,也就是一个应用只有一个windowSession

Session.addToDisplay实际上走的是WMS的addWindow方法,后面的逻辑就交给WMS去处理了,WMS就会创建window,然后结合参数计算window的高度等等,最后使用viewRootImpl进行绘制。

window的添加过程是通过PhoneWindow对应的WindowManagerImpl来添加window,内部会调用WindowManagerGlobal来实现。WindowManagerGlobal会使用viewRootImpl来进行跨进程通信让WMS执行创建window的业务。

每个应用都有一个windowSession,用于负责和WMS的通信,如ApplicationThread与AMS的通信。

Other Window

dialog和popupwindow的层级是10001999,在这个层级都属于子window而不是应用Window(199),子Window需要附属于父Window(Activity,也就是应用Window)才能显示,dialog虽然创建了一个PhoneWindow,但是popupWindow最终也创建了一个Window,只是它不是PhoneWindow而已,popupWindow和dialog的显示都需要依赖父Window的Token,其实两者都需要依赖于Activiy

那么,PopupWindow则是在构造器中时将入参的contentView直接执行 setContentView(contentView);,然后在showAtLocation()中调用了preparePopup() 创建它的decorView(PopupDecorView)之后invokePopup()调用执行mWindowManager.addView(decorView, p);

  • 根据参数构建popupDecorView
  • 把popupDecorView添加到屏幕上

dismiss()中调用mWindowManager.removeViewImmediate(decorView);

Dialog

dialog的创建过程Activity比较像:构造器创建PhoneWindow,setContentView初始化DecorView,show时候添加DecorView。

  • 构造函数中创建PhoneWindow,设置WindowManger(这里拿的是传入的context,实际上这context只能是actiivty,的WindowManager)
  • Dialog.setContentView时掉PhoneWindow.setContentView来初始化DecorView
  • show()时候调用了mWindowManager.addView(mDecor, l);
  • dismiss()时候调用mWindowManager.removeViewImmediate(mDecor);

总结

  • dialog和popupWindow不同,dialog创建了新的PhoneWindow,使用了PhoneWindow的DecorView模板。而popupWindow没有,popupWindow他也对应一个window,因为它也是通过windowManager添加上去的,不属于Activity的view树。
  • dialog的显示层级数更高,会直接显示在Activity上面,在dialog后添加的popUpWindow也会显示在dialog下
  • dialog的创建流程和activity非常像

概念梳理//待整理

关键类

前面的源码流程中涉及到很多的类,这里把相关的类统一分析一下。先看一张图:

window内部关键类

这基本上是我们这篇文章涉及到的所有关键类。

(图中绿色的window并不是一个类,而是表意上的window)

另外ViewRootImpl并不在PhoneWindow中,而是在View.attachInfo中,也被WindowManagerService中持有维护)

window相关

window的实现类只有一个:PhoneWindow,他继承自Window抽象类。后面我会重点分析他。

WindowManager相关

顾名思义,windowManager就是window管理类。这一部分的关键类有windowManager,viewManager,windowManagerImpl,windowManagerGlobal。windowManager是一个接口,继承自viewManager。viewManager中包含了我们非常熟悉的三个接口:addView,removeView,updateView
windowManagerImpl和PhoneWindow是成对出现的,前者负责管理后者。WindowManagerImpl是windowManager的实现类,但是他本身并没有真正实现逻辑,而是交给了WindowManagerGlobal。WindowManagerGlobal是全局单例,windowManagerImpl内部使用桥接模式,他是windowManager接口逻辑的真正实现

view相关

这里有个很关键的类:ViewRootImpl。每个view树都会有一个。当我使用windowManager的addView方法时,就会创建一个ViewRootImpl。ViewRootImpl的作用很关键:

  • 负责连接view和window的桥梁事务
  • 负责和WindowManagerService的联系
  • 负责管理和绘制view树
  • 事件的中转站

每个window都会有一个ViewRootImpl,viewRootImpl是负责绘制这个view树和window与view的桥梁,每个window都会有一个ViewRootImpl。

WindowManagerService

这个是window的真正管理者,类似于AMS(ActivityManagerService)管理四大组件。所有的window创建最终都要经过windowManagerService。整个Android的window机制中,WMS绝对是核心,他决定了屏幕所有的window该如何显示如何分发点击事件等等。

从Android架构角度看Window

前面我们介绍过关于PhoneWindow和window之间的关系,了解到PhoneWindow其实不是Window,只是一个window容器。不知读者有没想过一个问题,为什么谷歌要建一个不是window但却名字是window的类?是故意要迷惑我们吗?要了解这个问题,我们先来回顾一下整个android的window机制结构。

首先从WindowManagerService开始,我们知道WMS是window的最终管理者,在WMS中为每一个应用持有一个session,关于session前面我们讲过,每个应用都是全局单例,负责和WMS通信的binder对象。WMS为每个window都建立了一个windowStatus对象,同一个应用的window使用同个session进行跨进程通信,结构大概如下:

WMS结构

而负责与WMS通信的,是viewRootImpl。前面我们讲过每个view树即为一个window,viewRootImpl负责和WMS进行通信,同时也负责view的绘制。如果把上面的图画仔细一点就是:

更详细的结构图

图中每一个windowStatus对应一个viewRootImpl,WMS通过viewRootImpl来控制view。这也就是window机制的管理结构。当我们需要添加window的时候,最终的逻辑实现是WindowManagerGlobal,他的内部使用自己的session创建一个viewRootImpl,然后向WMS申请添加window,结构图大概如下:

window的添加结构

windowManagerGlobal使用自己的IWindowSession创建viewRootImpl,这个IWindowSession是全局单例。viewRootImpl和WMS申请创建window,然后WMS允许之后,再通知viewRootImpl绘制view,同时WMS通过windowStatus存储了viewRootImpl的相关信息,这样如果WMS需要修改view,直接通过viewRootImpl就可以修改view了。


从上面的描述中可以发现我全程没有提及到PhoneWindow和WindowManagerImpl。这是因为他们不属于window机制内的类,而是封装于window机制之上的框架。假设如果没有PhoneWindow和WindowManager我们该如何添加一个window?首先需要调用WindowGlobal获取session,再创建viewRootImpl,再访问wms,然后再利用viewRootImpl绘制view,是不是很复杂,而这仅仅只是整体的步骤。而WindowManagerImpl正是这个功能。他内部拥有WindowManagerGlobal的单例,然后帮助我们完成了这一系列的步骤。同时,windowManagerImpl也是只有一个实例,其他的windowManagerImpl都是建立在windowManagerImpl单例上。这一点在前面有通过源码介绍到。

另外,上面我讲到PhoneWindow并不是window而是一个辅助Activity管理的工具类,那为什么他不要命名为windowUtils呢?首先,PhoneWindow这个类是谷歌给window机制进行更上一层的封装。PhoneWindow内部拥有一个DecorView,我们的布局view都是添加到decorView中的,因为我们可以通过给decorView设置背景,宽高度,标题栏,按键反馈等等,来间接给我们的布局view设置。这样一来,PhoneWindow的存在,向开发者屏蔽真正的window,暴露给开发者一个“存在的”window。我们可以认为PhoneWindow就是一个window,window是view容器。当我们需要在屏幕上添加view的时候,只需要获得应用window对应的windowManagerImpl,然后直接调用addView方法添加view即可。这里也可以解释为什么windowManager的接口方法是addView而不是addWindow,一是window确实是以view的存在形式没错,二是为了向开发者屏蔽真正的window,让我们以为是在往window中添加view,window是真实存在的东西。他们的关系画个图如下:

window整体结构

黄色部分输于谷歌提供给开发者的window框架,而绿色是真正的window机制结构。通过PhoneWindow我们可以很方便地进行window操作,而不须了解底层究竟是如何工作的。PhoneWindow的存在,更是让window的“可见性”得到了实现,让window变成了一个“view容器”。

好了最后来总结一下:

  • Android内部的window机制与谷歌暴露给我们的api是不一样的,谷歌封装的目的是为了让我们更好地使用window。
  • dialog、popupWindow等框架更是对具体场景进行更进一步的封装。
  • 我们在了解window机制的时候,需要跳过应用层,看到window的本质,才能更好地帮助我们理解window。
  • 在android的其他地方也是一样,利用封装向开发者屏蔽底层逻辑,让我们更好地运用。但如果我们需要了解他的机制的时候,就需要绕过这层封装,看到本质。

window与PhoneWindow的关系

解释一下标题,window是指window机制中window这个概念,而PhoneWindow是指PhoneWindow这个类。后面我在讲的时候,如果是指类,我会在后面加个‘类’字。如window是指window概念,window类是指window这个抽象类。读者不要混淆。

还记得我在讲window的概念的时候留了一个思考吗?

思考:Android中不是有一个抽象类叫做window还有一个PhoneWindow实现类吗,他们不就是window的存在形式,为什么说window是抽象不存在的

这里我再抛出几个问题:

  • 有一些资料认为PhoneWindow就是window,是view容器,负责管理容器内的view,windowManagerImpl可以往里面添加view,如上面我们讲过的addView方法。但是,同时它又说每个window对应一个viewRootImpl,但却没解释为什么每次addView都会新建一个viewRootImpl,前后发送矛盾。
  • 有一些资料也是认为PhoneWindow是window,但是他说addView方法不是添加view而是添加window,同时拿这个方法的名字作为论据证明view就是window,但是他没解释为什么在使用addView方法创建window的过程却没有创建PhoneWindow对象。

我们一步步来看。我们首先来看一下源码中对于window抽象类的注释:

复制代码

1
2
3
4
5
6
7
 Abstract base class for a top-level window look and behavior policy.  An
instance of this class should be used as the top-level view added to the
window manager. It provides standard UI policies such as a background, title
area, default key processing, etc.

顶层窗口外观和行为策略的抽象基类。此类的实例应用作添加到窗口管理器的顶层视图。
它提供标准的UI策略,如背景、标题区域、默认键处理等。

大概意思就是:这个类是顶级窗口的抽象基类,顶级窗口必须继承他,他负责窗口的外观如背景、标题、默认按键处理等。这个类的实例被添加到windowManager中,让windowManager对他进行管理。PhoneWindow是一个top-level window(顶级窗口),他被添加到顶级窗口管理器的顶层视图,其他的window,都需要添加到这个顶层视图中,所以更准确的来说,PhoneWindow并不是view容器,而是window容器。

PhoneWindow是Window吗?

PhoneWindow并不是view容器,而是window容器。

那PhoneWindow的存在意义是什么?

PhoneWindow只是提供了 token 机制来校验子window的合法性

第一、提供DecorView模板。

如下图:

img

我们的Activity是通过setContentView把布局设置到DecorView中,那么DecorView本身的布局,就成为了Activity界面的背景。同时DecorView是分为标题栏和内容两部分,所以也可以可界面设置标题栏。同时,由于我们的界面是添加在的DecorView中,属于DecorView的一部分。那么对于DecorView的window属性设置也会对我们的布局界面生效。还记得谷歌的官方给window类注释的最后一句话吗:它提供标准的UI策略,如背景、标题区域、默认键处理等。这些都可以通过DecorView实现,这是PhoneWindow的第一个作用。

第二、抽离Activity中关于window的逻辑。

Activity的职责非常多,如果所有的事情都自己做,那么会造成本身代码极其臃肿。阅读过Activity启动的读者可能知道,AMS也通过ActivityStarter这个类来抽离启动Activity启动的逻辑。这样关于window相关的事情,就交给PhoneWindow去处理了。(事实上,Activity调用的是WindowManagerImpl,但因PhoneWindow和WindowManagerImpl两者是成对存在,他们共同处理window相关的事务,所以这里就简单写成交给PhoneWindow处理。)当Activity需要添加界面时,只需要一句setContentView,调用了PhoneWindow的setContentView方法,就把布局设置到屏幕上了。具体怎么完成,Activity不必管。

第三、PhoneWindow提供了 token 机制来校验子window的合法性

添加window需要有token,而token只有PhoneWindow拥有;因此其他的window都必须直接或间接与Activity的PhoneWindow有联系

PhoneWindow内部有一个token属性,用于验证一个PhoneWindow是否允许添加window。在Activity创建PhoneWindow的时候,就会把从AMS传过来的token赋值给他,从而他也就有了添加token的权限。而其他的PhoneWindow则没有这个权限,因而也无法添加window。

校验 token 的过程主要发生在 WindowManagerService 中。当应用程序请求操作窗口时,比如添加、移动、更新或者移除窗口,系统会通过传递 token 来确定该操作是否合法。如果 token 是无效的,系统将拒绝该操作,校验条件包括

  1. 权限检查:系统会检查当前应用程序是否有权限执行特定的窗口操作。例如,如果一个应用程序没有 SYSTEM_ALERT_WINDOW 权限,它就无法显示悬浮窗口。
  2. 安全性检查:系统可能会检查窗口的 token 是否有效、是否来自可信任的应用程序等。这可以防止恶意应用程序伪造窗口 token,尝试执行未经授权的窗口操作。
  3. 焦点和层级检查:系统可能会检查窗口的焦点状态和层级关系,以确保窗口的显示不会干扰到其他窗口或系统功能的正常运行。

附录 常见type&flag

AndroidDeveloper.Window flag and type

window的type属性

前面我们讲到window机制解决的一个问题就是view的显示次序问题,这个属性就决定了window的显示次序。window是有分类的,不同类别的显示高度范围不同,例如我把1-1000m高度称为低空,1001-2000m高度称为中空,2000以上称为高空。window也是一样按照高度范围进行分类,他也有一个变量Z-Order,决定了window的高度。window一共可分为三类:

  • 应用程序窗口:应用程序窗口一般位于最底层,Z-Order在1-99
  • 子窗口:子窗口一般是显示在应用窗口之上,Z-Order在1000-1999
  • 系统级窗口:系统级窗口一般位于最顶层,不会被其他的window遮住,如Toast,Z-Order在2000-2999。如果要弹出自定义系统级窗口需要动态申请权限

Z-Order越大,window越靠近用户,也就显示越高,高度高的window会覆盖高度低的window。

window的type属性就是Z-Order的值,我们可以给window的type属性赋值来决定window的高度。系统为我们三类window都预设了静态常量,如下(以下常用参数介绍转自参考文献第一篇文章):

  • 应用级window

    复制代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 应用程序 Window 的开始值
    public static final int FIRST_APPLICATION_WINDOW = 1;

    // 应用程序 Window 的基础值
    public static final int TYPE_BASE_APPLICATION = 1;

    // 普通的应用程序
    public static final int TYPE_APPLICATION = 2;

    // 特殊的应用程序窗口,当程序可以显示 Window 之前使用这个 Window 来显示一些东西
    public static final int TYPE_APPLICATION_STARTING = 3;

    // TYPE_APPLICATION 的变体,在应用程序显示之前,WindowManager 会等待这个 Window 绘制完毕
    public static final int TYPE_DRAWN_APPLICATION = 4;

    // 应用程序 Window 的结束值
    public static final int LAST_APPLICATION_WINDOW = 99;
  • 子window

    复制代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 子 Window 类型的开始值
    public static final int FIRST_SUB_WINDOW = 1000;

    // 应用程序 Window 顶部的面板。这些 Window 出现在其附加 Window 的顶部。
    public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

    // 用于显示媒体(如视频)的 Window。这些 Window 出现在其附加 Window 的后面。
    public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;

    // 应用程序 Window 顶部的子面板。这些 Window 出现在其附加 Window 和任何Window的顶部
    public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;

    // 当前Window的布局和顶级Window布局相同时,不能作为子代的容器
    public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;

    // 用显示媒体 Window 覆盖顶部的 Window, 这是系统隐藏的 API
    public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;

    // 子面板在应用程序Window的顶部,这些Window显示在其附加Window的顶部, 这是系统隐藏的 API
    public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;

    // 子 Window 类型的结束值
    public static final int LAST_SUB_WINDOW = 1999;
  • 系统级window

    复制代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    // 系统Window类型的开始值
    public static final int FIRST_SYSTEM_WINDOW = 2000;

    // 系统状态栏,只能有一个状态栏,它被放置在屏幕的顶部,所有其他窗口都向下移动
    public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;

    // 系统搜索窗口,只能有一个搜索栏,它被放置在屏幕的顶部
    public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;

    // 已经从系统中被移除,可以使用 TYPE_KEYGUARD_DIALOG 代替
    public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;

    // 系统对话框窗口
    public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;

    // 锁屏时显示的对话框
    public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;

    // 输入法窗口,位于普通 UI 之上,应用程序可重新布局以免被此窗口覆盖
    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_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;

    // 系统Window类型的结束值
    public static final int LAST_SYSTEM_WINDOW = 2999;

Window的flags参数

flag标志控制window的东西比较多,很多资料的描述是“控制window的显示”,但我觉得不够准确。flag控制的范围包括了:各种情景下的显示逻辑(锁屏,游戏等)还有触控事件的处理逻辑。控制显示确实是他的很大部分功能,但是并不是全部。下面看一下一些常用的flag,就知道flag的功能了(以下常用参数介绍转自参考文献第一篇文章):

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 当 Window 可见时允许锁屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;

// Window 后面的内容都变暗
public static final int FLAG_DIM_BEHIND = 0x00000002;

// Window 不能获得输入焦点,即不接受任何按键或按钮事件,例如该 Window 上 有 EditView,点击 EditView 是 不会弹出软键盘的
// Window 范围外的事件依旧为原窗口处理;例如点击该窗口外的view,依然会有响应。另外只要设置了此Flag,都将会启用FLAG_NOT_TOUCH_MODAL
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;

// 设置了该 Flag,将 Window 之外的按键事件发送给后面的 Window 处理, 而自己只会处理 Window 区域内的触摸事件
// Window 之外的 view 也是可以响应 touch 事件。
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;

// 设置了该Flag,表示该 Window 将不会接受任何 touch 事件,例如点击该 Window 不会有响应,只会传给下面有聚焦的窗口。
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;

// 只要 Window 可见时屏幕就会一直亮着
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;

// 允许 Window 占满整个屏幕
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;

// 允许 Window 超过屏幕之外
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;

// 全屏显示,隐藏所有的 Window 装饰,比如在游戏、播放器中的全屏显示
public static final int FLAG_FULLSCREEN = 0x00000400;

// 表示比FLAG_FULLSCREEN低一级,会显示状态栏
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;

// 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件
public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;

// 则当按键动作发生在 Window 之外时,将接收到一个MotionEvent.ACTION_OUTSIDE事件。
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;

@Deprecated
// 窗口可以在锁屏的 Window 之上显示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;

// 表示负责绘制系统栏背景。如果设置,系统栏将以透明背景绘制,
// 此 Window 中的相应区域将填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的颜色。
public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;

// 表示要求系统壁纸显示在该 Window 后面,Window 表面必须是半透明的,才能真正看到它背后的壁纸
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;

window的solfInputMode属性

这一部分就是当软件盘弹起来的时候,window的处理逻辑,这在日常中也经常遇到,如:我们在微信聊天的时候,点击输入框,当软键盘弹起来的时候输入框也会被顶上去。如果你不想被顶上去,也可以设置为被软键盘覆盖。下面介绍一下常见的属性(以下常见属性介绍选自参考文献第一篇文章):

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 没有指定状态,系统会选择一个合适的状态或者依赖于主题的配置
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;

// 当用户进入该窗口时,隐藏软键盘
public static final int SOFT_INPUT_STATE_HIDDEN = 2;

// 当窗口获取焦点时,隐藏软键盘
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;

// 当用户进入窗口时,显示软键盘
public static final int SOFT_INPUT_STATE_VISIBLE = 4;

// 当窗口获取焦点时,显示软键盘
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;

// window会调整大小以适应软键盘窗口
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;

// 没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;

// 当软键盘弹出时,窗口会调整大小,例如点击一个EditView,整个layout都将平移可见且处于软件盘的上方
// 同样的该模式不能与SOFT_INPUT_ADJUST_PAN结合使用;
// 如果窗口的布局参数标志包含FLAG_FULLSCREEN,则将忽略这个值,窗口不会调整大小,但会保持全屏。
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;

// 当软键盘弹出时,窗口不需要调整大小, 要确保输入焦点是可见的,
// 例如有两个EditView的输入框,一个为Ev1,一个为Ev2,当你点击Ev1想要输入数据时,当前的Ev1的输入框会移到软键盘上方
// 该模式不能与SOFT_INPUT_ADJUST_RESIZE结合使用
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;

// 将不会调整大小,直接覆盖在window上
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;

window的其他属性

上面的三个属性是window比较重要也是比较复杂 的三个,除此之外还有几个日常经常使用的属性:

  • x与y属性:指定window的位置
  • alpha:window的透明度
  • gravity:window在屏幕中的位置,使用的是Gravity类的常量
  • format:window的像素点格式,值定义在PixelFormat中

如何给window属性赋值

window属性的常量值大部分存储在WindowManager.LayoutParams类中,我们可以通过这个类来获得这些常量。当然还有Gravity类和PixelFormat类等。

一般情况下我们会通过以下方式来往屏幕中添加一个window:

复制代码

1
2
3
4
5
// 在Activity中调用
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
windParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
TextView view = new TextView(this);
getWindowManager.addview(view,windowParams);

我们可以直接给WindowManager.LayoutParams对象设置属性。

第二种赋值方法是直接给window赋值,如

复制代码

1
getWindow().flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;

除此之外,window的solfInputMode属性比较特殊,他可以直接在AndroidManifest中指定,如下:

复制代码

1
<activity android:windowSoftInputMode="adjustNothing" />

最后总结一下:

  • window的重要属性有type、flags、solfInputMode、gravity等
  • 我们可以通过不同的方式给window属性赋值
  • 没必要去全部记下来,等遇到需求再去寻找对应的常量即可

未整理

http://3dobe.com/archives/89/

一个Activity或一个Dialog对应一个PhoneWindow,一个PhoneWindow对应一个WindowManger,对应一个WindowManagerImpl

所以得

img

img

  • mChoreographer,Choreographer的实例,在SampleWindow的例子中已经见过了。Choreographer的意思是编舞指导。它拥有从显示子系统获取VSYNC同步事件的能力,从而可以在合适的时机通知渲染动作,避免在渲染的过程中因为发生屏幕重绘而导致的画面撕裂。从这个意义上讲,Choreographer的确是指导Android翩翩起舞的大师。WMS使用Choreographer负责驱动所有的窗口动画、屏幕旋转动画、墙纸动画的渲染。
  • mAnimator,WindowAnimator的实例。它是所有窗口动画的总管(窗口动画是一个WindowStateAnimator对象)。在Choreographer的驱动下,逐个渲染所有的动画。
  • mPolicy,WindowPolicyManager的一个实现。目前它只有PhoneWindowManager一个实现类。mPolicy定义了很多窗口相关的策略,可以说是WMS的首席顾问!每当WMS要做什么事情的时候,都需要向这个顾问请教应当如何做。例如,告诉WMS某一个类型的Window的ZOrder的值是多少,帮助WMS矫正不合理的窗口属性,会为WMS监听屏幕旋转的状态,还会预处理一些系统按键事件(例如HOME、BACK键等的默认行为就是在这里实现的),等等。所以,mPolicy可谓是WMS中最重要的一个成员了。
  • mDisplayContents,一个DisplayContent类型的列表。Android 4.2支持基于Wi-Fi Display的多屏幕输出,而一个DisplayContent描述了一块可以绘制窗口的屏幕。每个DisplayContent都用一个整型变量作为其ID,其中手机默认屏幕的ID由Display.DEFAULT_DISPLAY常量指定。DisplayContent的管理是由DisplayManagerService完成的,在本章不会去探讨DisplayContent的实现细节,而是关注DisplayContent对窗口管理与布局的影响
  • mTokenMap,一个HashMap,保存了所有的显示令牌(类型为WindowToken),用于窗口管理。在SampleWindow例子中曾经提到过,一个窗口必须隶属于某一个显示令牌。在那个例子中所添加的令牌就被放进了这个HashMap中。从这个成员中还衍生出几个辅助的显示令牌的子集,例如mAppTokens保存了所有属于Activity的显示令牌(WindowToken的子类AppWindowToken),mExitingTokens则保存了正在退出过程中的显示令牌等。其中mAppTokens列表是有序的,它与AMS中的mHistory列表的顺序保持一致,反映了系统中Activity的顺序。
  • mWindowMap,也是一个HashMap,保存了所有窗口的状态信息(类型为WindowState),用于窗口管理。在SampleWindow例子中,使用IWindowSession.add()所添加的窗口的状态将会被保存在mWindowMap中。与mTokenMap一样,mWindowMap一样有衍生出的子集。例如mPendingRemove保存了那些退出动画播放完成并即将被移除的窗口,mLosingFocus则保存了那些失去了输入焦点的窗口。在DisplayContent中,也有一个windows列表,这个列表存储了显示在此Display-Content中的窗口,并且它是有序的。窗口在这个列表中的位置决定了其最终显示时的Z序。
  • mSessions,一个List,元素类型为Session。Session其实是SampleWindow例子中的IWindowSession的Bn端。也就是说,mSessions这个列表保存了当前所有想向WMS寻求窗口管理服务的客户端。注意Session是进程唯一的。
  • mRotation,只是一个int型变量。它保存了当前手机的旋转状态

Android 系统采用一种称为 Surface 的图形架构,简而言之,每一个 Activity 都关联有至少一个 Window(窗口),每一个 Window 都对应有一个 Surface

Surface 这里直译过来叫做 绘图表面 ,顾名思义,其可在内存中生成一个图形缓冲区队列,用于描述 UI,经与系统服务的WindowServiceManager 通信后、通过 SurfaceFlinger 服务持续合成并送显到显示屏。

2.png

由此可见,通常情况下,一个 ActivityUI 渲染本质是 系统提供一块内存,并创建一个图形缓冲区进行维护;这块内存就是 Surface,最终页面所有 ViewUI 状态数据,都会被填充到同一个 Surface 中。

Author

white crow

Posted on

2022-02-16

Updated on

2024-03-25

Licensed under