一箫一剑平生意,负尽狂名十五年。这篇文章主要讲述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;
}
这一类中, 系统预定义了几个变量, 我们来看一下:
文章图片
根据这张表小伙伴们可以发现, 应用窗口的层值不会大于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的窗口
以上。
推荐阅读
- Android App 优化之消除卡顿
- Android Meun 用法
- Android SDK国内代理速度还可以
- 第三方框架之ThinkAndroid 学习总结
- Android NDk环境配置
- android adb 命令详解
- android studio和eclipse中如何获取sha1值
- Please install Android target
- Android-adb 常用命令 和 sqlite