WmS详解之如何理解Window和窗口的关系(基于Android7.0源码)

一箫一剑平生意,负尽狂名十五年。这篇文章主要讲述WmS详解之如何理解Window和窗口的关系?基于Android7.0源码相关的知识,希望能为你提供帮助。
【WmS详解之如何理解Window和窗口的关系(基于Android7.0源码)】上篇博客( WmS详解(一)之token到底是什么? 基于Android7.0源码) 中我们简要介绍了token的作用, 这里涉及到的概念非常多, 其中出现频率最高的要数Window和窗口这一对搭档了, 那么我们今天就来看看到底我们该如何理解android系统中的Window和窗口。
窗口这个概念, 从不同的角度来看它的含义不一样, 如果我们从WmS( WindowManagerService) 的角度来看窗口, 那么这个窗口并不是一个Window类, 而是一个View。用户发来的消息被WmS接收之后并不能直接发给各个View, 真正接收用户消息的是IWindow类, IWindow类的实现类是ViewRootImpl.W类, 每一个W类内部都有一个View变量, WmS在收到用户发来的消息之后, 判断哪一个窗口处于活动状态, 找到之后将用户消息交给W类, W类再把用户消息传递给内部的View变量, 剩下的事情就是View对象来搞定了。总的来说, 在WmS眼中, 窗口就是一个View, 本文后面提到窗口都是指一个View; Window则是对窗口行为的进一步提取和抽象, Window将窗口的一些公共行为抽取出来统一处理, 相当于Window是窗口的一个子集。
OK, 说完了窗口的定义, 接下来我们来看看窗口的类型。
窗口分类根据窗口的type属性, 窗口可以分为三大类, 分别是应用窗口、子窗口和系统窗口, View的添加都是通过WindowManagerImpl类中的addView方法来实现的, 该方法最终调用了WindowManagerGlobal中的addView方法, 我们来看看这个方法:

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { if (view = = null) { throw new IllegalArgumentException(" view must not be null" ); } if (display = = null) { throw new IllegalArgumentException(" display must not be null" ); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException(" Params must be WindowManager.LayoutParams" ); }final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; ...... ......// If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type > = WindowManager.LayoutParams.FIRST_SUB_WINDOW & & wparams.type < = WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews.size(); for (int i = 0; i < count; i+ + ) { if (mRoots.get(i).mWindow.asBinder() = = wparams.token) { panelParentView = mViews.get(i); } } }root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); }...... ......}


该方法接收四个参数, 第一个参数表示要添加的View, 第二个参数表示该View的参数, 第三个参数表示要输出的显示设备, 第四个参数表示该View的父布局, 其中第二个参数params要求必须是WindowManager.LayoutParams的实例, 这个参数params中有一个参数type就是用来标记每一个窗口的类型, 这个类型实际上是一个int值, 这个int值表示窗口的层, WmS在进行窗口叠加的时候, 按照int常量大小分配不同的层, int值越大, View越靠近上层, int值越小, View越靠近下层。接下我们就来详细看一看这三种不同的窗口类型:

应用窗口Activity对应的窗口就是应用窗口, 但是由于Activity的加载是由AmS来完成的, 因此, 应用窗口的创建实际上是由AmS来完成的。所有的Activity默认的窗口类型都是TYPE_APPLICATION, 这个从WindowManager.LayoutParams的构造方法中就可以看出:

public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = TYPE_APPLICATION; format = PixelFormat.OPAQUE; }


这一类中, 系统预定义了几个变量, 我们来看一下:
WmS详解之如何理解Window和窗口的关系(基于Android7.0源码)

文章图片

根据这张表小伙伴们可以发现, 应用窗口的层值不会大于99, 而系统在进行窗口叠加的时候会自动为窗口分配不同大小的层值。
子窗口子窗口所以为子窗口是因为它有一个父窗口, 父窗口可以是任意类型的其他窗口, 我们在开发中常见的子窗口有PopupWindow、Dialog、ContextMenu、PopupMenu等。关于子窗口, 系统也定义了几种类型, 我们来看一下:
WmS详解之如何理解Window和窗口的关系(基于Android7.0源码)

文章图片

当我们创建子窗口时, 我们可以指定窗口类型介于1000~ 1999之间, WmS在进行窗口叠加时, 会动态调整层值。

系统窗口常见的系统窗口有状态栏、导航栏( 国内部分Android手机有) 、发生ANR时的提示框、输入法窗口、Toast窗口、锁屏时显示的屏保以及各种管家自带的那种加速球。系统窗口不需要有对应的Activity窗口, 它也不需要父窗口, 大部分情况下我们见到的系统窗口都是由系统创建的, 当然我们也可以自己来创建系统窗口, 和另外两种类型的窗口一样, 系统也帮我们创建了一部分系统窗口常量:
WmS详解之如何理解Window和窗口的关系(基于Android7.0源码)

文章图片

事实上, 在Android7.0中, 系统一共定义了三十多个系统窗口常量, 但是有一部分目前并没有使用, 所以我这里就没有列出来。当我们创建系统窗口时, 我们可以指定系统窗口的层值在2000-2999之间, WmS在进行窗口叠加时, 会动态调整该层值, 但是该值会介于2000-2999之间。


窗口创建看了很多理论知识, 接下来我们来看看我们自己怎么样来创建窗口。

创建子窗口假设我当前页面有一个Button, 当我点击Button的时候, 弹出一个子窗口, 效果如下:
WmS详解之如何理解Window和窗口的关系(基于Android7.0源码)

文章图片

我们来看看代码:

public void btnClick(View view) { IBinder token = view.getWindowToken(); WindowManager.LayoutParams lp = new WindowManager.LayoutParams(200, 200, 0, 0, PixelFormat.TRANSPARENT); lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; lp.token = token; final TextView tv = new TextView(this); tv.setText(" 我是弹出子窗口" ); tv.setBackgroundColor(Color.BLUE); wm = getWindowManager(); tv.setOnKeyListener(new View.OnKeyListener() { @ Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (keyCode = = KeyEvent.KEYCODE_BACK) { MainActivity.this.wm.removeView(tv); } return false; } }); this.wm.addView(tv, lp); }


代码貌似没什么难度, 这里我就不做过多解释了。

创建系统窗口系统窗口可能是许多小伙伴使用较多的窗口创建方式, 常见的360悬浮球就是用这种方式实现的。这里我也举一个小例子:

public void addView(View view) { lp = new WindowManager.LayoutParams(80, 80, 0, 0, PixelFormat.TRANSPARENT); lp.flags = //Window不需要获取焦点, 该属性会自动开启FLAG_NOT_TOUCH_MODAL WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | //当前window区域内的事件自己处理, 区域外的事件交给底层的window处理 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | //锁屏状态下亦能显示window出来 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; lp.gravity = Gravity.LEFT | Gravity.TOP; //注意参考点为ActionBar的左上角 lp.x = 200; lp.y = 200; //type用来描述window的类型, window类型共分为三种: //应用级Window( 1-99) , 子Window( 1000-1999) , 系统Window( 2000-2999) //层级大的Window会覆盖掉层级小的Window lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; windowManager = getWindowManager(); windowManager.addView(tv, lp); }public void removeView(View view) { windowManager.removeView(tv); }


这里两个方法, 一个添加窗口, 一个移除窗口。但是使用系统窗口一般需要我们添加权限:

< uses-permission android:name= " android.permission.SYSTEM_ALERT_WINDOW" />



OK, 关于Window和窗口的介绍就说这么多吧, 有问题欢迎留言讨论。
参考资料:
1.浅析Android的窗口


以上。







    推荐阅读