上篇博客(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;
        }

这一类中,系统提前定义了几个变量。我们来看一下:

依据这张表小伙伴们能够发现,应用窗体的层值不会大于99。而系统在进行窗体叠加的时候会自己主动为窗体分配不同大小的层值。

子窗体

子窗体所以为子窗体是由于它有一个父窗体。父窗体能够是随意类型的其它窗体,我们在开发中常见的子窗体有PopupWindow、Dialog、ContextMenu、PopupMenu等。关于子窗体。系统也定义了几种类型,我们来看一下:

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

系统窗体

常见的系统窗体有状态栏、导航栏(国内部分Android手机有)、发生ANR时的提示框、输入法窗体、Toast窗体、锁屏时显示的屏保以及各种管家自带的那种加速球。系统窗体不须要有相应的Activity窗体,它也不须要父窗体。大部分情况下我们见到的系统窗体都是由系统创建的,当然我们也能够自己来创建系统窗体,和另外两种类型的窗体一样。系统也帮我们创建了一部分系统窗体常量:

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


窗体创建

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

创建子窗体

如果我当前页面有一个Button,当我点击Button的时候。弹出一个子窗体,效果例如以下:

我们来看看代码:

    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的窗体


以上。