Android-Window机制

Android-Window机制

1. Window的表现形式

1.1 什么是Window

在大部分 PC 操作系统中,Window 指的是一个可视化的窗口区域。例如在 MacOS、Linux、Windows 系统中,可视化内容的 Window 通常包含(并非必须)「关闭按钮」、「最小化」、「最大化」,这个 Window 是由操作系统分配的且具有 UI(例如边框、按钮、标题等),限定了内容显示的区域,可视化的内容在渲染时只需要相对给定的 Window 渲染和绘制,因此提供给了用户自由操作的空间。

1.2 Android中的Window

在 Android 中,Window 更多只是一种抽象的概念:一组 View 的集合,因为 Android 中的 Window 并没有实际的 UI。在源码中有 Window, PhoneWindow, ViewManager, WindowManager, WindowManagerImpl, WindowManagerGlobal 等与 Window 相关的类,但这些都不是 Window 实体。

Android 中 Window 和 View 的关系就像「组织」和「个体」的关系,一群相似的个体可以组成一个集体并对外具有一定属性、功能,但「组织」只是一个概念上的集合,并不是一个实际存在的东西。


2. Window属性

Window 有 3 个重要属性:

  • type
  • flags
  • solfInputMode

2.1 Type

由于 Android 中 Window 是对一组 View 集合的描述,不同应用有不同的 Window,同一个应用也可能有多个 Window,所以需要用一种优先级表示 Window 的显示层级,在 Android 中使用 type 表示 Window 的类型,实际上是通过 type 的大小表示 Window 的优先级,值越大则在屏幕上显示位置越上层,会覆盖低层级的 Window:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {

public int type;

// 应用的主窗口层级范围 1 ~ 99
public static final int FIRST_APPLICATION_WINDOW = 1;
......
public static final int LAST_APPLICATION_WINDOW = 99;

// 应用子窗口(例如 Dialog)层级范围 1000 ~ 1999
public static final int FIRST_SUB_WINDOW = 1000;
......
public static final int LAST_SUB_WINDOW = 1999;

// 系统窗口层级范围 2000 ~ 2999
public static final int FIRST_SYSTEM_WINDOW = 2000;
......
public static final int LAST_SYSTEM_WINDOW = 2999;
}
}

2.2 Flags

Flags 用于指定 Window 在不同场景下的处理方式,包括样式(全屏、背景等)、显示模式(是否允许锁屏、是否允许超出屏幕等)、触摸事件处理逻辑(是否接受触摸响应、是否将外部触摸事件传递到下层窗口等):

1
2
3
4
5
6
7
8
9
10
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {

public int flags;

// 所有 Flag 枚举
public static final int FLAG_XXX = xxxxx;

}
}

2.3 SoftInputMode

SoftInputMode 决定了 Window 在弹出软键盘时的逻辑,实际上软键盘也是一种 Window,但因为比较特殊所以专门用了一个属性来控制:

1
2
3
4
5
6
7
8
9
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {

public int softInputMode;

// 所有 SoftInputMode 枚举
public static final int SOFT_INPUT_XXX = xxxxx;
}
}

3. 创建Window

3.1 指定用于创建Window的View

在创建一个 Window 时,例如在 Activity 手动调用 getWindowManager().addView(...),或是 Dialog#show,本质上都是调用了 WindowManager 实现类的 WindowManagerImpl#addView 方法,实际上 WindowManagerImpl 内的方法都只是对入参做了一些校验和预处理,最终都是通过桥接模式调用单例的 WindowManagerGlobal 实现的:

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
49
50
51
52
53
54
public final class WindowManagerImpl implements WindowManager {

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}

public final class WindowManagerGlobal {

// 单例
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}

// 实际创建 Window
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
if (...) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
}
}
}
}

WindowManagerGlobal#addView(...) 主要做了五件事:

  • 校验入参;
  • 如果 parentWindow != null 说明需要创建 SubWindow,则 SubWindow 需要继承部分 parentWindow 的属性;
  • 创建 ViewRootImpl;
  • 把用于创建 Window 的 View、ViewRootImpl、应用到 Window 的 LayoutParams 分别存入三个列表,这三个列表总是等长的,三列表中同一下标对应的三个对象均指向了同一个 Window;
  • 将用于创建 Window 的 View 以及 LayoutParams 传入 ViewRootImpl,由 ViewRootImpl 实际创建 Window。

这里也能看出,每次创建 Window 时都会先创建一个 ViewRootImpl,而真正创建 Window 的步骤其实就在 ViewRootImpl 内,并不是 WindowManager 也不是 PhoneWindow,ViewRootImpl 与创建的 Window 是一一对应的。

3.2 ViewRootImpl创建Window

ViewRootImpl 在构造方法中通过单例的 WindowManagerGlobal 获取了一个单例的 IWindowSession 对象,用于在 ViewRootImpl#setView(...) 中真正创建 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
36
37
38
public final class ViewRootImpl implements ViewParent {

final IWindowSession mWindowSession;

public ViewRootImpl(Context context, Display display) {
mWindowSession = WindowManagerGlobal.getWindowSession();
// 绑定了初始化时的线程
mThread = Thread.currentThread();
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// 传入的 view 就是 DecorView。
synchronized (this) {
if (mView == null) {
mView = view;
......
// 调用 WindowSession#addToDisplay(...),才是真正创建了一个 Window。
res = mWindowSession.addToDisplay(...);
}
}
}
}

public final class WindowManagerGlobal {

private static IWindowSession sWindowSession;

public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(...);
}
}
return sWindowSession;
}
}
}

这也能看出,WindowManagerGlobal 和 IWindowSession 都是应用内单例的;

  • WindowManagerGlobal 是一个工具类,用于维护和提供一系列与 Window 相关的资源,例如在 addView(...) 中就创建并维护了 View、ViewRootImpl、LayoutParams,而 getWindowSession(...) 则维护并提供 IWindowSession 对象,但 WindowManagerGlobal 本身并不承担创建 Window 的责任,是一个纯粹的工具类。

  • IWindowSession 包含了一系列与 WindowManagerService 的交互逻辑:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {

    final WindowManagerService mService;

    @Override
    public int addToDisplay(...) {
    return mService.addWindow(...);
    }
    }

所以,关于 Window、PhoneWindow、ViewManager、WindowManager、WindowManagerImpl、WindowManagerGlobal、ViewRootImpl 的关系可以总结如下:

  • 首先在 Activity#attach(...) 阶段先创建 new PhoneWindow(...)
  • 然后通过 PhoneWindow#setWindowManager(...) 的内部创建出来一个 WindowManagerImpl;
  • 所有创建 Window 的地方,不论是 Activity、Dialog、还是 PopupWindow,最终都会调用 WindowManagerImpl#addView(...)
  • WindowManagerImpl 则调用单例的 WindowManagerGlobal#addView(...)
  • WindowManagerGlobal 创建 new ViewRootImpl(...)
  • ViewRootImpl 通过 setView(...) 持有了用于创建 Window 的 View,以及指定 Window 属性的 LayoutParams,因此 ViewRootImpl 负责连接 View 和 Window
  • ViewRootImpl 从 WindowManagerGlobal 中获取单例的 IWindowSession,调用 IWindowSession#addToDisplay(...)
  • IWindowSession 调用 WMS 完成最终的 Window 创建,因此 IWindowSession 负责连接 ViewRootImpl 和 WMS

除了创建窗口,WindowManagerImpl 还能通过 updateView(...)removeView(...) 更新及移除 Window。


4. PhoneWindow和ViewRootImpl

4.1 PhoneWindow不是Window

通过上文分析会发现,创建 Window 最终都是通过 WindowManagerImpl#addView(...) 实现的,而 WindowManagerImpl 又是在 PhoneWindow#setWindowManager(...) 中创建的,那为什么 PhoneWindow 不是一个实际的 Window 呢?这个问题需要从两个方面考虑:

(1)PhoneWindow 与 Window 并不是一一对应关系:

首先,PhoneWindow 和实际显示在屏幕上的 Window 并不是一一对应的关系。尽管在 Activity 启动流程中创建了一个 new PhoneWindow(...),但例如 PopupWindow、或是手动调用 getWindowManager().addView(...) 创建一个 Window 时,都并没有创建 PhoneWindow,而且它们最终都是用这个 Activity 的 WindowManagerImpl 来创建的,与 Activity 创建的 PhoneWindow 并没有直接关系。即便是 Dialog 在构造方法中创建了属于自己的 PhoneWindow 对象,但 show() 的时候用到的 WindowManagerImpl 也是通过 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 从传入的 Context 中获取的,而创建 Dialog 传入的 Context 又几乎肯定是 Activity,所以实际上这些创建 Window 的过程,仅仅只和 WindowManagerImpl 有关,与 PhoneWindow 并没有什么关系。

(2)PhoneWindow 并没有与 WMS 的交互:

Android 对 Window 真正的创建、更新、和移除,都是通过 IWindowSession 调用 WindowManagerService 实现的;反之 WMS 也是通过 IWindowSession 来分配、调度、以及管理实际的 Window。而每个 App 只有一个单例的 IWindowSession,保存在同样单例的 WindowManagerGlobal 中,只有 ViewRootImpl 获取并操作了 IWindowSession,而 ViewRootImpl 又是 WindowManagerImpl 每次调用 addView(...) 时创建的,因此这个与 WMS 交互的过程同样与 PhoneWindow 无关。

4.2 PhoneWindow的意义

上文中提到,Activity 在 attach(...) 时创建了自己的 PhoneWindow,而 Dialog 也在构造方法中创建了自己的 PhoneWindow,乍一看似乎 PhoneWindow 就是一个实际的 Window,当然通过上文也已经知道并非如此。PhoneWindow 与 Window 的关系,非常类似于 Socket 与 TCP / UDP 的关系:

  • TCP / UDP 都是实际上的传输层协议,但 Socket 既不参与实际数据协议的转换(传输层)、又不负责维护设备之间的通信信道(会话层,Socket 虽然可以建立连接,但 Socket 只是帮忙发起了请求,本质上这条连接并不是 Socket 维护的,而是操作系统维护的)、也不负责传输数据的编解码(表示层),因为 Socket 并不属于 OSI 模型中的任何一层,Socket 只是对 TCP / UDP 的封装而并不参与到实际网络传输中的缓解,应该把 Socket 看成一个工具。
  • 而 PhoneWindow 与 Window 的关系也是同理:
    • PhoneWindow 维护了与 Window 相关的资源,例如 WindowManager、Type 属性、Flags 属性、SoftInputMode 属性等;
    • 并提供了一系列与 Window 相关的工具方法,例如加载 DecorView、设置 Title、获取实际 Window 的一些状态等;
    • 封装了对 DecorView、SubDecor 的加载;
    • 当需要改变 Window 的属性时,实际上是把对应的属性设置到 PhoneWindow 中,然后由 ViewRootImpl 请求 WMS 后读取并按照对应的属性设置 Window。

当然上文也提到,Dialog 会在构造方法中创建自己的 PhoneWindow,这是因为 Dialog 是一个很灵活的组件,本质上 Dialog 其实就是另一个 Window,但开发人员对 Dialog 的 Window 有很大的自定义需求,经常需要修改 Window 的属性,因此 Dialog 对创建出来的 Window 资源做了一层封装,放在自己的 PhoneWindow 中,而对于 PopupWindow 或手动调用 getWindowManager().addView(...) 创建的 Window,只是 Google 没有提供获取 Window 并修改属性的 API 而已。

4.3 ViewRootImpl的意义

通过分析 Window 的创建流程可以发现,Window 创建真正相关的其实是 WindowManagerImpl 和 ViewRootImpl,甚至可以说直接相关的只有 ViewRootImpl,因为 DecorView 也是在 ViewRootImpl 中维护的。而上文也提到 Android 中 Window 只是一种概念,Android 中的 Window 是用一组 ViewTree 表示的,因为 ViewTree 具有从上到下的层级关系,所以实际上 Window 可以与 ViewTree 的顶点一一对应,那这个顶点应该是什么呢?

答案就是 ViewRootImpl,而不是 DecorView。尽管在 Activity 启动流程以及 Window 的创建流程中,DecorView 才是实际上 ViewTree 最上层的 View,而且 DecorView 本身也继承自 View,但是 View 最主要的功能还是承担 UI 上的显示,用一个 View 来承担与 Window 的交互从设计模式上是不合理的,而 ViewRootImpl 对上承担了 View 对 Window 的请求,对下承担了 Window 对 View 的事件分发,因此把 ViewRootImpl 看作 ViewTree 的顶点更合适。个人认为,这也是为什么 ViewRootImpl 会实现 ViewParent 接口的原因,这么设计使得 ViewRootImpl 也可以看成 View 的 Parent,例如每一个 View 在需要刷新 UI 的时候都会调用 invalidate()requestLayout(),最终也都会向上传递到 ViewRootImpl,然后由 ViewRootImpl 统一处理渲染、VSync 信号处理等等逻辑,而 ViewTree 中实际的每个 View 只需要根据 ViewRootImpl 分发的回调处理即可。


5. Window加载时机

5.1 Activity的Window

Activity 启动流程 中对 Activity 的启动做了分析,Activity 的 Window 是在 ActivityThread#handleResumeActivity(...) 中调用 wm.addView(decor, l); 创建的:

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
public final class ActivityThread extends ClientTransactionHandler {
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
// 先调用了 performResumeActivity
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
......
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 然后才创建 Window
wm.addView(decor, l);
}
}
}
}
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
String reason) {
......
try {
// 调用到 Activity 内
r.activity.performResume(r.startsNotResumed, reason);
}
}
}

最终调用了 Activity#performResume(...)

1
2
3
4
5
6
7
public class Activity {
final void performResume(boolean followedByPause, String reason) {
......
// 这一步已经回调了 Activity 的 onResume() 生命周期
mInstrumentation.callActivityOnResume(this);
}
}

通过源码可以看出,Activity 在回调 onResume() 生命周期的时候,Window 以及 ViewRootImpl 是还没有创建的!

5.2 创建新Window

上文已经得出结论,Activity 在 onResume() 执行完返回之后,才会执行创建 Window 和 ViewRootImpl 的逻辑;

  • 这就带来了一个问题:在 Activity 生命周期的 onResume 回调中,可以创建 Window 吗?例如弹出一个 PopupWindow、或是调用 getWindowManager().addView(...)、或是弹出一个 Dialog?
  • 答案:
    • Dialog 可以。
    • PopupWindow 默认情况下不能,需要指定某个范围内的 Type,否则抛出异常。
    • 手动调用 getWindowManager().addView(...) 只有在 Window 的 Type 属性在某个范围内才可以,否则抛出异常。

抛出的异常是:android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?,抛出的地方位于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final class ViewRootImpl {
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// 首先把 DecorView 存为成员变量 mView
if (mView == null) {
mView = view;
}
......
try {
// 通过 IWindowSession 实际请求 WMS 创建 Window:
res = mWindowSession.addToDisplay(...);
}
if (res < WindowManagerGlobal.ADD_OKAY) {
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
// 就是在这里抛出的异常,Token 为 null。
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
}
}
}

通过上文分析可知,创建一个 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public final class WindowManagerImpl implements WindowManager {
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}

public final class WindowManagerGlobal {
// 实际创建 Window
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
// 如果 parentWindow 不为空,说明当前已经存在一个 Window 了,
// 就需要用 parentWindow 的属性对新 Window 的 LayoutParams 做预处理。
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
try {
// 将 DecorView 传入 ViewRootImpl。
root.setView(view, wparams, panelParentView);
}
}
}
}

public abstract class Window {
// 这个方法的意义就在于,如果新的窗口是一个 SubWindow,则它需要继承一些父 Window 的属性,
// 例如 Token,SubWindow 会使用父 Window 的 Token 作为创建自己的授权。
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
// 注意这里最外层的三个条件,对 Window 的 Type 做了判断
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
// 当 (1000 <= Type <= 1999) 时,也就是 SubWindow,则 Token 是通过 DecorView 获取的
if (wp.token == null) {
View decor = peekDecorView();
if (decor != null) {
wp.token = decor.getWindowToken();
}
}
......
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
// 当 (2000 <= Type <= 2999) 时,此时新 Window 是一个系统 Window,
// 因此不需要继承什么属性,生命周期也应该独立,源码注释也有说明。
} else {
// 否则此时新 Window 的 Type 范围就是:(1 <= Type <= 99),属于 Application 类型,
// 则 Token 直接复用当前 Window 的 Token。
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
}
}
}

可以看到在 Window#adjustLayoutParamsForSubWindow(LayoutParams) 中对新 Window 的判断有三个大分支,按优先级顺序:

  • 1000 <= Type <= 1999
  • 2000 <= Type <= 2999
  • 1 <= Type <= 99

5.2.1 作为SubWindow时

当新 Window 1000 <= Type <= 1999 时,说明新 Window 是一个 SubWindow,SubWindow 的前提就是已经存在父 Window,因此 Token 就从父 Window 的 DecorView 里面获取:

1
2
3
4
5
6
7
8
9
10
11
12
// getWindowToken() 其实是 View 的方法
public class View {
// dispatchAttachedToWindow 存入 AttachInfo,
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
}
// getWindowToken 就从存入的 AttachInfo 里面取出 Token
public IBinder getWindowToken() {
// 可以看到返回的是 mAttachInfo.mWindowToken
return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
}
}

DecorView#getWindowToken() 获取的实际上是从 View 成员变量 mAttachInfo 获取的,因此就需要找到这个 mAttachInfo 是什么时候被存入 DecorView 的,也就是 dispatchAttachedToWindow(...) 是什么时候调用的:

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
public final class ActivityThread extends ClientTransactionHandler {
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
// 创建 Window 时传入的就是 DecorView
wm.addView(decor, l);
}
}

public final class WindowManagerGlobal {
// 入参的 View 就是 DecorView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
try {
// 将 DecorView 传入 ViewRootImpl。
root.setView(view, wparams, panelParentView);
}
}
}
}

public final class ViewRootImpl {
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// 首先把 DecorView 存为成员变量 mView
if (mView == null) {
mView = view;
}
}
private void performTraversals() {
// 这里 mView 就是 DecorView,用局部变量 host 持有:
final View host = mView;
......
// 就是在这里把 AttachInfo 存入了 DecorView 中。
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
}

从源码中可以分析得出:

  • WindowManagerGlobal 创建 ViewRootImpl 之后调用 ViewRootImpl#setView(...) 将 DecorView 存入 ViewRootImpl;
  • ViewRootImpl 在实际开始渲染第一帧的时候才将 AttachInfo 存入 DecorView;
  • 这之后 DecorView#getWindowToken() 才不为空。
  • 因此当新建的 Window 1000 <= Type <= 1999 时,只有在 DecorView 第一帧渲染之后才能获取到 Token。

5.2.2 作为SystemWindow时

2000 <= Type <= 2999 时,说明新 Window 是一个 SystemWindow,则不需要从其他 Window 继承任何属性,也不需要复用其他 Window 的 Token,Android 将以独立生命周期处理这个 Window,在源码中也没有做什么处理,这种情况下是不会抛出异常的。

5.2.3 作为ApplicationWindow时

当上述两个条件均不满足时,说明 1 <= Type <= 99,也即新 Window 是一个 ApplicationWindow,则新 Window 就直接复用当前 Window 的 Token,又因为 WindowManagerGlobal 中调用的是 parentWindow.adjustLayoutParamsForSubWindow(LayoutParams),所以新 Window 就是复用的 parentWindow 的 Token,对应在 Activity 中就是 Activity 的 Window 的 Token,再进一步也就是 Activity#attach(...) 阶段从 AMS 传进来的 Token,因此新 Window 在这个阶段(只是预处理 LayoutParams 的阶段,还没有创建 ViewRootImpl)就已经具有了 Token,因此在后续创建 ViewRootImpl 并调用 ViewRootImpl#setView(...) 时也就不会抛出异常。

5.2.4 问题分析

上文 3 个小节已经对新 Window 的 Type 取不同值时,对应 Token 的获取时机做了分析,因此对于 Activity#onCreate() 时是否能创建新 Window,就能通过新 Window 的 Type 取值做判断了:

(1)PopupWindow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PopupWindow {
// 实际取值:1000,属于 SubWindow 的范围
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;

public void setWindowLayoutType(int layoutType) {
mWindowLayoutType = layoutType;
}
public int getWindowLayoutType() {
return mWindowLayoutType;
}
public void showAtLocation(IBinder token, int gravity, int x, int y) {
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
}
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
final WindowManager.LayoutParams p =
createPopupLayoutParams(anchor.getApplicationWindowToken());
}
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.type = mWindowLayoutType;
}
}

可以看到,PopupWindow 在成员变量中已经指定了默认的 Type == 1000,因此默认情况下的 PopupWindow 在 Activity#onResume() 阶段无法通过 DecorView 获取到 Token,会抛出异常,但可以通过手动设置 popupWindow.setWindowLayoutType(1 ~ 99) 直接复用 Activity 的 Window 的 Token 解决问题。

(2)手动调用 getWindowManager.addView(...)

1
2
3
4
5
6
7
getWindowManager().addView(textView, new WindowManager.LayoutParams(
100, // 宽
100, // 高
TYPE_APPLICATION, // Type 值,手动指定在 1 ~ 99 即可
0, // Flags
0 // Bitmap 的像素格式
));

由于可以手动指定 Type 属性,因此指定在 1 ~ 99 范围内即可。

(3)Dialog:

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
public class Dialog {
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
final Window w = new PhoneWindow(mContext);
mWindow = w;
}
public void show() {
WindowManager.LayoutParams l = mWindow.getAttributes();
}
}

public abstract class Window {
// Window 的 LayoutParams 有默认值
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
}

public interface WindowManager extends ViewManager {
public static class LayoutParams {
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
// WindowManager.LayoutParams 的默认 Type 就是 TYPE_APPLICATION
type = TYPE_APPLICATION;
}
}
}

可以看到,Dialog 在构造方法中创建了自己的 PhoneWindow,而 Window 默认的 LayoutParams 中 Type == TYPE_APPLICATION == 2,所以 Dialog 也是直接复用了当前 Window 的 Token。


参考文献