Window的使用及原理

Window在实际开发中使用的不是很多,不过,一些特殊需要使用Window去实现。比如,在桌面上显示一个悬浮窗,就需要使用到Window。

Window是一个抽象类,它只有一个具体实现类PhoneWindow。创建Window需要使用到WindowManager,它是外界访问Window的入口,不过,对Window的具体操作是由WindowManagerService实现的,WindowManager和WindowManagerService通过Binder进行交互。

Android中所有的视图都是通过Window来呈现的,比如Activity、Dialog和Toast等,它们的视图实际上都是附加在一个Window上,因此,Window实际上是View的直接管理者。

Window的使用

下面代码展示了通过WindowManager添加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
private Button mFloatingButton; // Window要展示的View
private WindowManager.LayoutParams mLayoutParams; // Window的布局参数

private void createWindow() {
// 初始化Window要展示的View
mFloatingButton = new Button(this);
mFloatingButton.setText("Window");

// 初始化Window的布局参数
mLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.gravity = Gravity.START | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
}

@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
// 使用WindowManager创建Window,并向Window中添加View
getWindowManager().addView(mFloatingButton, mLayoutParams);
}

@Override
public void onDetachedFromWindow() {
// 使用WindowManager从Window中移除View,并消耗Window
getWindowManager().removeView(mFloatingButton);
super.onDetachedFromWindow();
}

如果Window需要能够被拖拽,那么需要为Window的View设置监听器:

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
mFloatingButton.setOnTouchListener(new View.OnTouchListener() {
private int offsetX, offsetY;

@Override
public boolean onTouch(View v, MotionEvent event) {
// 获取触摸点坐标
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录当前触摸点相对View左上角的偏移量
offsetX = mLayoutParams.x - x;
offsetY = mLayoutParams.y - y;
return true;
case MotionEvent.ACTION_MOVE:
// 根据偏移量重新布局View,使View移动产生拖拽效果
mLayoutParams.x = x + offsetX;
mLayoutParams.y = y + offsetY;
// 使用WindowManager更新Window的布局参数,改变View的位置
getWindowManager().updateViewLayout(mFloatingButton, mLayoutParams);
return true;
}
return false;
}
});

在使用Window过程中,需要注意WindowManager.LayoutParams中的flags和type参数,下面分别进行说明。

flags参数表示Window的属性,可以控制Window的显示特性,常用的选项有:

  • FLAG_NOT_FOCUSABLE

表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终输入事件会传递给下层具有焦点的Window。

  • FLAG_NOT_TOUCH_MODAL

在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。一般来说都需要开启此标记,否则其他Window无法接收到单击事件。

  • FLAG_SHOW_WHEN_LOCKED

开启此模式可以让Window显示在锁屏的界面上。

type参数表示Window的类型,一共有三种类型:应用Window、子Window和系统Window。应用Window对应一个Activity;子Window不能单独存在,需要附属在特定的父Window上,比如Dialog;系统Window需要声明对应的权限才能被创建,比如Toast和StatusBar。

另外,Window是分层的,每个Window都有层级,层级大的会覆盖在层级小的Window上面。在上面介绍的三类Window中,应用Window的层级范围是1~99,子Window的层级范围是1000~1999,系统Window的层级范围是2000~2999,而这些层级范围对应相应的type参数值。如果使用系统层级,需要声明相关权限,比如使用TYPE_SYSTEM_ERROR系统层级,要声明权限。

Window的原理

WindowManager介绍

WindowManager是操作Window的入口,它只提供了三个方法分别用于添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承于ViewManager。

1
2
3
4
5
6
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}

因此,WindowManager操作Window本质上是操作Window中的View。

Window的内部机制

Window是一个抽象的概念,每一个Window都对应一个View和一个ViewRootImpl,Window和View通过ViewRootImpl建立联系。因此,Window并不实际存在,它是以View的形式存在的。在实际使用中无法直接访问Window,而是要通过WindowManager访问。

Window是通过WindowManager进行操作的,它是一个接口,具体实现是WindowManagerImpl类。WindowManagerImpl并没有直接实现Window的具体操作,而是交给WindowManagerGlobal处理。在WindowManagerGlobal中,会创建View对应的ViewRootImpl对象,通过ViewRootImpl对象实现View的添加、更新和删除操作。不过,ViewRootImpl对View操作最终通过IPC方式交由WindowManagerService进行处理。

坚持原创技术分享,您的支持将鼓励我继续创作!