Android Window

  1. Android 中所有的视图都是通过 Window 来呈现的,像常用的 Activity,Dialog 和 Toast 都是附加在 Window 上的,所以 Window 是 View 的直接管理者
  2. Window 是个抽象概念,每一个 Window 对应着一个 ViewRootImpl,Window 通过 ViewRootImpl 来和 View 建立联系。View 是 Window 存在的实体,只能通过 WindowManager 访问 Window。
  3. WindowManager 是一个接口,继承 ViewManager 接口。主要用来管理窗口的一些状态、属性,View 的添加、删除、更新、消息收集和处理等。其唯一实现类是 WindowManagerImpl,WindowManagerImpl 又委托给了 WindowManagerGlobal。
  4. WindowManagerGlobal 中存储着四个 List,分别是 mViewsmRootsmParamsmDyingViews。单例模式。
  5. Window 的操作通过 IPC 通信交给 WindowManagerService 操作。

Window

Window 相关类

Window Uml

  • Window: 是一个抽象类,提供了统一的外观和行为标准。作为顶级视图添加到 WindowManager 中,View 是依附于 Window 而存在的,对 View 进行管理。View 是 window 的存在形式,window 是 view 的载体
  • PhoneWindow: Window 的唯一实现类。Activity,Toast 和 Dialog 都包含 PhoneWindow 对象,PopupWindow 。
  • WindowManager: 是一个接口,继承了 ViewManager(addView,updateViewLayout 和 removeView 方法) 接口,对 Window 进行管理。实现类是 WindowManagerImpl。
  • WindowManagerGlobal: 单例模式。对 Window 的操作都通过它代理执行。
  • ViewRootImpl: 实现了 ViewParent 接口,是 Window 和 DecorView 的桥梁,类似 ActivityThread 和 AMS 通信一样。内部类 W 是一个 Binder 对象,它通静态变量 sWindowSession(Session 实例 与 WMS 进行通信。绘制 View 和 事件传递。

Activity 是控制器。Window 是承载器,承载视图。DecorView 是顶层视图,负责视图部分。ViewRoot 是连接器,负责用户和 WMS 的交互。

Window LayoutParams

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
// Window.java
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
public static final int FIRST_APPLICATION_WINDOW = 1;
public static final int TYPE_BASE_APPLICATION = 1;
public static final int LAST_APPLICATION_WINDOW = 99;

public static final int FIRST_SUB_WINDOW = 1000;
public static final int LAST_SUB_WINDOW = 1999;

public static final int FIRST_SYSTEM_WINDOW = 2000;
public static final int LAST_SYSTEM_WINDOW = 2999;

public int x;
public int y;
/**
* The general type of window. There are three main classes of
* window types:Application windows,Sub-windows,System windows
*/
public int type;
/**
* Various behavioral options/flags. Default is none.
*/
public int flags;
/**
* Identifier for this window. This will usually be filled in for you.
*/
public IBinder token = null;
}

Window Type

Window 有三种类型,分别是应用 Window子Window系统 Window
Window 是分层的,应用 Window 是 1-99,子 Window 是 1000-1999,系统 Window 是 2000-2999。层级大的会覆盖层级小的 Window 上面。

应用窗口:

Activity 对应的窗口类型是应用窗口, 所有 Activity 默认的窗口类型是 TYPE_BASE_APPLICATION。 WindowManager 的 LayoutParams 的默认类型是 TYPE_APPLICATION。 Dialog 并没有设置 type,所以也是默认的窗口类型即 TYPE_APPLICATION。

子窗口:

子窗口不能单独存在,它需要附属在父 Window 中,比如 PopupWindow 或 Dialog。

系统窗口:

一般来讲,系统窗口应该由系统来创建的,例如 ANR 时的提示框,系统状态栏,屏保等。但是,Framework 还是定义了一些,可以被应用所创建的系统窗口,如 TYPE_APPLICATION_OVERLAY。系统窗口需要相应的权限。

常量值 类型
FIRST_APPLICATION_WINDOW=1 第一个普通应用窗口
TYPE_BASE_APPLICATION=1 所有程序窗口的 base 窗口,其他应用程序窗口都显示在它上面
TYPE_APPLICATION=2 普通应用程序窗口,token 必须设置为 Activity 的 token 来指定窗口属于谁
TYPE_APPLICATION_STARTING=3 应用程序启动时先显示此窗口,当真正的窗口配置完成后,关闭此窗口
LAST_APPLICATION_WINDOW=99 最后一个应用窗口
FIRST_SUB_WINDOW = 1000 第一个子 Window
LAST_SUB_WINDOW = 1999 最后一个子 Window
FIRST_SYSTEM_WINDOW=2000 第一个系统窗口
TYPE_STATUS_BAR=2000 状态栏,只能有一个状态栏,位于屏幕顶端
TYPE_SYSTEM_DIALOG=2008 系统对话框
TYPE_INPUT_METHOD=2011 输入法窗口,显示于普通应用/子窗口之上
TYPE_APPLICATION_OVERLAY=2038 应用弹窗
LAST_SYSTEM_WINDOW=2999 最后一个系统窗口

注意:
TYPE_PHONE、TYPE_SYSTEM_ALERT、TYPE_TOAST、TYPE_SYSTEM_OVERLAY、TYPE_PRIORITY_PHONE、TYPE_SYSTEM_ERROR 在 API 26 中已废弃,使用 TYPE_APPLICATION_OVERLAY 代替,需要申请 Manifest.permission.SYSTEM_ALERT_WINDOW 权限。
TYPE_KEYGUARD 已经被从系统中移除,使用 TYPE_KEYGUARD_DIALOG 代替。

Window Flag

Window 属性用来控制 Window 的显示特性,这里主要介绍几个比较常用的选项:

  • FLAG_NOT_FOCUSABLE

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

  • FLAG_NOT_TOUCH_MODAL

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

Window 类型和标记通过 WindowManager.LayoutParams 设置:

1
2
3
4
5
6
7
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
}

Window Token

在源码中 token 一般代表的是 Binder 对象,用于 IPC 通讯。并且它也包含着此次通讯所需要的信息,在 ViewRootImpl 里,token 用来表示 mWindow(W 类,即 IWindow),并且在 WMS 中只有符合要求的 token 才能让 Window 正常显示。

在 Window 中,token(LayoutParams.token)分为以下 2 种情况:

  1. 应用窗口 : 表示的是 activity 的 mToken
  2. 子窗口 : 表示的是父窗口的 W 对象,也就是 mWindow(IWindow)

Window 添加 View 的过程

1
2
3
4
5
// Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;

public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 初始化 mDecor 和 mContentParent 对象
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}

// Activity 转场动画
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 将布局资源加载到 mContentParent 中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
// 让 DecorView 的内容区域延伸到 systemUi 下方,达到全屏、透明状态栏的效果
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
// 回调 Content 变化通知
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

private void installDecor() {
if (mDecor == null) {
// 实例化 DecorView
mDecor = generateDecor(-1);
} else {
// 关联 Window
mDecor.setWindow(this);
}
if (mContentParent == null) {
//并将mContentParent绑定至 id=R.id.content 的 ViewGroup。
// 绑定 id=R.id.content 的 View
mContentParent = generateLayout(mDecor);
// 处理转场动画
}
}

protected DecorView generateDecor(int featureId) {
// 关联 Window
return new DecorView(context, featureId, this, getAttributes());
}

protected ViewGroup generateLayout(DecorView decor) {
// 获取 Window 样式
// 定义在 ~\frameworks\base\core\res\res\values\attrs.xml
TypedArray a = getWindowStyle();

// 根据样式,设置悬浮窗,titleBar,actionBar,全屏,透明状态栏等

// 更新 Window 参数
WindowManager.LayoutParams params = getAttributes();

int layoutResource;
int features = getLocalFeatures();
// 根据不同的 feature 加载不同的 layout 资源
if(features & xxx != 0){
layoutResource = R.layout.xxx;
} else {
// 默认的 layout
layoutResource = R.layout.screen_simple;
}

// 将布局添加至 DecorView 中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 配置完成,DecorView 根据已有属性调整布局状态
mDecor.finishChanging();
return contentParent;
}
}
1
2
3
4
5
6
7
8
9
10
11
// DecorView.java
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
// ...
} else {
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- screen_simple.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
  1. 首次调用 Activity#setContentView() 时通过 installDecor() 方法初始化 mDecor 和 mContentParent(R.id.content) 对象。
    • generateDecor() 创建 DecorView 对象。
    • generateLayout(mDecor) 填充布局。首先根据当前的主题设置相关 feature,获取布局文件(默认为 screen_simple.xml),然后将布局添加到 DecorView 中,并将 mContentParent 与布局中 id=R.id.content 的 FrameLayout 绑定。
  2. 如果设置了 FEATURE_CONTENT_TRANSITIONS,就会创建 Scene 完成转场动画;否则使用布局填充器将布局文件填充至 mContentParent。Activity 的布局文件也就添加到 DecorView 里面了。

Activity setContentView

Window 的创建过程

Activity 通过 ClassLoader 实例化后调用 Activity#attach() 方法,在该方法中创建 PhoneWindow 对象。

1
2
3
4
5
6
7
public final class ActivityThread extends ClientTransactionHandler {
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
activity.attach(appContext, this, getInstrumentation(), ...);
//回调 Activity#onCreate() 方法
mInstrumentation.callActivityOnCreate(activity, r.state);
}
}

通过 AMS 启动 Activity,回调 performLaunchActivity() 方法。

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
public class Activity extends ContextThemeWrapper
implements Window.Callback, Window.OnWindowDismissedCallback ...{
private Thread mUiThread;
ActivityThread mMainThread;
private IBinder mToken;
private Window mWindow;
private WindowManager mWindowManager;

final void attach(Context context, ActivityThread aThread, IBinder token,...){
// 实例化 PhoneWindow 对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 设置回调,接收事件、Window Attached 和 Detached、内容改变通知
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mMainThread = aThread;
mToken = token;
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, ...);
mWindowManager = mWindow.getWindowManager();
}

@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
}

在 Activity#onCreate() 方法里,我们通过 setContentView() 将 view 添加到 PhoneWindow 的 mContentParent(R.id.content) 中,也就是将资源布局文件和 PhoneWindow 关联。虽然 Window 和 DecorView 已经创建并初始化完毕,但这个时候的 DecorView 还没有被 WindowManager 添加到 Window 中。

Window 的添加过程

PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。真正完成把一个 View 作为窗口添加到 WMS 的过程是由 WindowManager 来完成的。

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
// ActivityThread.java

public void handleResumeActivity(IBinder token, ...) {
final ActivityClientRecord r = performResumeActivity(token, ...);
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
// 隐藏 DecorView
decor.setVisibility(View.INVISIBLE);
// 获取 WMS
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
// Activity 窗口类型为 TYPE_BASE_APPLICATION
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 把 DecorView 添加到窗口上
wm.addView(decor, l);
}
}
}

if (!r.activity.mFinished && willBeVisible &&
r.activity.mDecor != null && !r.hideForNow) {
if (r.activity.mVisibleFromClient) {
// 显示 DecorView
r.activity.makeVisible();
}
}

}

通过 WindowManagerImpl 将 DecorView 添加到窗口上 wm.addView(decor, l),又交给了 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
55
56
57
58
59
60
61
62
63
64
65
66
67
public final class WindowManagerGlobal {
private static IWindowManager sWindowManagerService;
private static IWindowSession sWindowSession;

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
// parentWindow 即 PhoneWindow
if (parentWindow != null) {
// 根据窗口类型设置 token,titile等参数
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
// 查找子 Window 的父 Window
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 绘制流程,并添加到 window上
root.setView(view, wparams, panelParentView, userId);
}
}

public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
// 通过 ServiceManager 获取 IWindowManager 的 proxy
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
}
return sWindowManagerService;
}
}

public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
IWindowManager windowManager = getWindowManagerService();
// 通过 WMS 实例化 Session 对象
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
}
return sWindowSession;
}
}
}

接着通过 ViewRootImpl#setView() 设置 DecorView。

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
59
60
61
62
63
public final class ViewRootImpl implements ViewParent ...{
final IWindowSession mWindowSession;
final View.AttachInfo mAttachInfo;

public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(), false);
}

public ViewRootImpl(Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
mWindowSession = session;
mWindow = new W(this);
// 创建 AttachInfo 对象
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow ...);
}

public void setView(View view, WindowManager.LayoutParams attrs, ...) {
mView = view;
mAdded = true;

// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();

// mWindowSession 是一个 Binder 对象
// 返回 WindowManagerGlobal.ADD_OKAY=0 表示添加窗口成功,其他 12 种错误码都是负数
int res = mWindowSession.addToDisplayAsUser(mWindow, ...)
if (res < WindowManagerGlobal.ADD_OKAY) {
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- permission denied for window type "
+ mWindowAttributes.type);
case WindowManagerGlobal.ADD_INVALID_TYPE:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified window type "
+ mWindowAttributes.type + " is not valid");
...
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);

// DecorView 的 parent 是 ViewRootImpl
view.assignParent(this);
}

// 应用端 W 的实例 mWindow 在应用进程是 native 端,用来监听窗口变化。
static class W extends IWindow.Stub {
@Override
public void insetsChanged(InsetsState insetsState) {}

}
}

mWindowSession 通过 WindowManagerGlobal.getWindowSession() 方法获取,是一个 Binder 对象。因为 WindowManagerGlobal 是单例,所以 mWindowSession 对象每个进程只有一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* This class represents an active client session. There is generally one
* Session object per process that is interacting with the window manager.
*/
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public Session(WindowManagerService service, IWindowSessionCallback callback) {}

@Override
public int addToDisplayAsUser(IWindow window, ...) {
// 通过 WMS 添加窗口
return mService.addWindow(this, window, seq, ...);
}
}

每一个 Session 在 WidowManagerService 里代表一个与应用进程打开的会话连接,每个进程只有一个 Session 对象。最终 Session 通过 IPC 通信,请求 WMS 添加 Window,并由 W 类接收通知。

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
public class WindowManagerService extends IWindowManager.Stub...{
final ArraySet<Session> mSessions = new ArraySet<>();
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
WindowManagerPolicy mPolicy;

public int addWindow(Session session, IWindow client, ...){

// mPolicy 是 PhoneWindowManager 实例化
// 根据 type 检查窗口类型是否合法,如果是系统窗口类型,还需要进行权限检查
int res = mPolicy.checkAddPermission(attrs.type, ...);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}

if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
// 父窗口不存在或父窗口也是子窗口,返回 ADD_BAD_SUBWINDOW_TOKEN
}
// 一系列 type 判断

// 创建一个 WindowState 对象,用于 WMS 表示一个窗口的对象
final WindowState win = new WindowState(this, session, client, token, ...);
if (type == TYPE_TOAST) {
mH.sendMessageDelayed(
mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
win.mAttrs.hideTimeoutMilliseconds);
}

res = WindowManagerGlobal.ADD_OKAY;
// 将 Session 添加到 mSessions 集合中
win.attach();
mWindowMap.put(client.asBinder(), win);
return res
}
}

Window 的添加请求就交给 WMS 去处理了,在 WMS 内部会为每一个应用保留一个单独的 Session,同时会创建一个 WindowState 对象来表示当前添加的窗口。
无论是应用窗口还是子窗口,token 不能为空。否则会抛出异常,并且应用窗口的 token 必须是某个 Activity 的 mToken,子窗口的 token 必须是父窗口的 IWindow 对象,而且子窗口还需等父窗口添加成功之后才可添加到 Window 上。

Window 添加流程图如下:
Window 添加过程
添加 Window IPC 交互
WindowState

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class PopupWindow {

public PopupWindow(Context context, AttributeSet attrs, ...) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

// 根据默认样式设置属性
// 设置显示和隐藏动画
}

public void setContentView(View contentView) {
if (isShowing()) {
return;
}
mContentView = contentView;
}

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
if (isShowing() || !hasContentView()) {
return;
}

attachToAnchor(anchor, xoff, yoff, gravity);

final WindowManager.LayoutParams p =
createPopupLayoutParams(anchor.getApplicationWindowToken());
preparePopup(p);

// 判断是否显示 anchorView 的上面
final boolean aboveAnchor = findDropDownPosition(anchor, ...);
updateAboveAnchor(aboveAnchor);

invokePopup(p);
}

private void preparePopup(WindowManager.LayoutParams p) {
if (mContentView == null || mContext == null || mWindowManager == null) {
throw new IllegalStateException("You must specify a valid content view by "
+ "calling setContentView() before attempting to show the popup.");
}
mDecorView = createDecorView(mBackgroundView);
}

private PopupDecorView createDecorView(View contentView) {
final PopupDecorView decorView = new PopupDecorView(mContext);
decorView.addView(contentView, MATCH_PARENT, height);
return decorView;
}

private void invokePopup(WindowManager.LayoutParams p) {
final PopupDecorView decorView = mDecorView;
mWindowManager.addView(decorView, p);
}

public void dismiss() {
if (!isShowing() || isTransitioningToDismiss()) {
return;
}

// 如果有动画,退出动画执行完毕后再 dismissImmediate()
// 通过 mWindowManager 移除 View
dismissImmediate(decorView, contentHolder, contentView);

// 移除相关监听器
detachFromAnchor();

//回调 onDismiss() 方法
if (mOnDismissListener != null) {
mOnDismissListener.onDismiss();
}
}

private class PopupDecorView extends FrameLayout {}
}
  1. 创建 PopupWindow,设置 ContentView 等其他属性。
  2. 调用 showAtLocation() 或 showAsDropDown() 方法,通过 WindowManager 添加 View。

注意:PopupWindow 并没有创建 PhoneWindow 对象。它和 Activity 在同一层级,不会在 Activity 上方。

Dialog 中的 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
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {

Dialog(@NonNull Context context, @StyleRes int themeResId,...) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);

mListenersHandler = new ListenersHandler(this);
}

public void setContentView(@NonNull View view) {
// 创建 DecorView
mWindow.setContentView(view);
}

public void show() {
if (mShowing) {
if (mDecor != null) {
mDecor.setVisibility(View.VISIBLE);
}
return;
}

mCanceled = false;
dispatchOnCreate(null);
onStart();
mDecor = mWindow.getDecorView();
WindowManager.LayoutParams l = mWindow.getAttributes();
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
}

private static final class ListenersHandler extends Handler {}
}

Dialog 是子窗口,依赖 Activity,需要 Activity 的 token,如果传递 ApplicationContext 会报 android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? 错误。

Window 的 SoftInputMode

总结

  1. 在 Window 系统中,分为两部分的内容:一部分是运行在系统服务进程的 WMS 及相关类,WMS 用 WindowState 来描述一个窗口;另一部分是运行在应用进程的 WindowManagerImpl, WindowManagerGlobal,ViewRootImpl 等相关类。
  2. 对于 WMS 来讲,窗口对应一个 View 对象,而不是 Window 对象。
  3. Window 是抽象类,描述是一类具有某种通用特性的窗口,唯一实现类是 PhoneWindow。PhoneWindow 类把一些操作的统一处理了,例如长按,按”Back”键等。
  4. Android Framework 把窗口分为三种类型,应用窗口,子窗口以及系统窗口。不同类型的窗口在执行添加窗口操作时,对于 WindowManager.LayoutParams 的 token 参数有不同的要求。
    应用窗口必须是某个有效的 Activity 的 mToken;子窗口的 token 必须是父窗口的 ViewRootImpl 中的 W 对象;有些系统窗口不需要 token,有些系统窗口的 token 必须满足一定的要求。
  5. 只能通过 Context.getSystemServer() 来获取 WindowManager。Activity 返回 mWindowManager,即 WindowManagerImpl。
  6. 在调用 WindowManagerImpl 的 addView 之前,如果没有给 token 赋值,则会走默认的 token 赋值逻辑。
    默认的 token 是如果 mParentWindow 不为空,则会调用其 adjustLayoutParamsForSubWindow() 方法。在 adjustLayoutParamsForSubWindow() 方法中,如果当前要添加的窗口是应用窗口,如果其 token 为空,则会把当前 PhoneWindow 的 mToken 赋值给 token。如果是子窗口,则会把当前 PhonwWindow 对应的 DecorView 的 mAttachInfo 中的 mWindowToken 赋值给 token。而 View 中的 mAttachIno 来自 ViewRootImpl 的 mAttachInfo。因此这个 token 本质就是父窗口的 ViewRootImpl 中的 W 类对象。

参考

[1] Android Window 机制探索
[2] Android 窗口创建流程