理解 Window 和 WindowManager

Window 和 WindowManager

Window 表示一个窗口的概念,是一个抽象类,具体实现是 PhoneWindow,它对 View 进行管理,位于 WindowManagerService 中。
可通过 WindowManager 来创建 Window,WindowManager 是一个接口类,继承自接口 ViewManager,它是用来管理 Window 的,WindowManager 的实现类为 WindowManagerImpl。如果想要对 Window(View)进行添加、更新和删除操作就可以使用 WindowManager,WindowManager 会将具体的工作交由 WMS 来处理。(关于 WMS,它的主要功能是管理 Window。)

WindowManager 和 WindowManagerService 的交互是一个 IPC 过程,通过 Binder 来进行跨进程通信,WMS 作为系统服务有很多 API 是不会暴露给 WindowManager 的,这一点与 ActivityManager 和 AMS 的关系有些类似。

Window 包含了 View 并对 View 进行管理,Window 用虚线来表示是因为 Window 是一个抽象概念,用来描述一个窗口,并不是真实存在的,Window 的实体其实也是 View,WindowManager 用来管理 Window,而 WindowManager 所提供的功能最终会由 WMS 进行处理。

Android 中所有的视图都是通过 Window 来呈现的。Window 分三类,并且是分层的。


WindowManager 的关联类

ViewManager

ViewManager 中定义了 3 个方法,分别用来添加、更新和删除 View。ViewManager.java

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 也继承了这些方法,而这些方法传入的参数都是 View 类型,说明 Window 是以 View 的形式存在的。WindowManager 在继承 ViewManager 的同时,又加入很多功能,包括 Window 的类型和层级相关的常量、内部类以及一些方法,其中有两个方法是根据 Window 的特性加入的:WindowManager.java

1
2
3
4
// 能够得知这个 WindowManager 实例将 Window 添加到哪个屏幕上了,换句话说,就是得到 WindowManager 所管理的屏幕(Display)。
public Display getDefaultDisplay();
// 规定在这个方法返回前要立即执行 View.onDetachedFromWindow(),来完成传入的 View 相关的销毁工作。
public void removeViewImmediate(View view);

PhoneWindow

在 Activity 启动过程中会调用 ActivityThread 的 performLaunchActivity 方法,performLaunchActivity 方法中又会调用 Activity 的 attach 方法,PhoneWindow 就是在这个方法中创建的。Activity.java

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
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// 创建了 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
// 调用了 PhoneWindow 的 setWindowManager 方法,此方法在 PhoneWindow 的父类 Window 中实现。
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;

mWindow.setColorMode(info.colorMode);
}

接下来查看 Window 中的 setWindowManager 方法。Window.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
// 如果 WindowManager 为 null,调用 Context 的 getSystemService 方法,并传入服务的名称 Context.WINDOW_SERVICE(值为 window)。最后得到的是 WindowManagerImpl 实例,并在此转为 WindowManager 类型。
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// 调用了 WindowManagerImpl 的 createLocalWindowManager 方法,此方法同样也是创建 WindowManagerImpl,不同的是这次创建 WindowManagerImpl 时将创建它的 Window 作为参数传了进来,这样 WindowManagerImpl 就持有了 Window 的引用,可以对 Window 进行操作,比如在 Window 中添加 View,会调用 WindowManagerImpl 的 addView 方法。
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

接下来查看 WindowManagerImpl 的 addView 方法。WindowManagerImpl.java

1
2
3
4
5
6
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// 调用了 WindowManagerGlobal 的 addView 方法,最后一个参数就是上面提到的 Window。
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

可以看出,WindowManagerImpl 虽然是 WindowManager 的实现类,但是没有实现什么功能,而是将功能实现委托给了 WindowManagerGlobal,这里用到的是桥接模式。

接下来查看 WindowManagerImpl 是如何定义 WindowManagerGlobal 的。WindowManagerImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class WindowManagerImpl implements WindowManager {
// 可以看出 WindowManagerGlobal 是一个单例,说明在一个进程中只有一个 WindowManagerGlobal 实例。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
...
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
// 说明这个 WindowManagerImpl 实例会作为哪个 Window 的子 Window,这也就说明在一个进程中 WindowManagerImpl 可能会有多个实例。
mParentWindow = parentWindow;
}
...
}

PhoneWindow 继承自 Window,Window 通过 setWindowManager 方法与 WindowManager 发生关联。WindowManager 继承自接口 ViewManager,WindowManagerImpl 是 WindowManager 接口的实现类,但是具体的功能都会委托给 WindowManagerGlobal 来实现。


Window 的属性

对于 Window、WindowManager、WMS 之间的关系,WMS 是 Window 的最终管理者,Window 好比是员工,WMS 是老板,为了方便老板管理员工则需要定义一些 “协议”,这些 “协议” 就是 Window 的属性,它们被定义在 WindowManager 的内部类 LayoutParams 中,了解 Window 的属性能够更好地理解 WMS 的内部原理。Window 的属性有很多种,与应用开发最密切的的有 3 种,它们分别是 Type(Window 的类型)、Flag(Window 的标志)和 SoftInputMode(软键盘相关模式)。

Window 的类型和显示次序

Window 的类型有很多种,比如应用程序窗口、系统错误窗口、输入法窗口、PopupWindow、Toast、Dialog 等。总的来说 Window 分为三大类型,分别是 Application Window(应用程序窗口)、Sub Window(子窗口)、System Window(系统窗口),每个大类型中又包含了很多种类型,它们都定义在 WindowManager 的静态内部类 LayoutParams 中。WindowManager.java

应用程序窗口

Activity 就是一个典型的应用程序窗口,应用程序窗口包含的 Type 值类型如下:

1
2
3
4
5
6
7
8
9
10
11
// 此 Type 表示应用程序窗口类型初始值。
public static final int FIRST_APPLICATION_WINDOW = 1;
// 窗口的基础知识,其他的窗口值要大于这个值。
public static final int TYPE_BASE_APPLICATION = 1;
// 普通的应用程序窗口类型。
public static final int TYPE_APPLICATION = 2;
// 应用程序启动窗口类型,用于系统在应用程序窗口启动前显示的窗口。
public static final int TYPE_APPLICATION_STARTING = 3;
public static final int TYPE_DRAWN_APPLICATION = 4;
// 此 Type 值表示应用程序窗口类型结束值,也就是说应用程序窗口的 Type 值范围是 1~99,这个数值的大小涉及窗口的层级。
public static final int LAST_APPLICATION_WINDOW = 99;

子窗口

它不能独立存在,需要附着在其它窗口才可以,比如 PopupWindow 就是子窗口。子窗口的类型定义如下:

1
2
3
4
5
6
7
8
9
10
// 子窗口的类型初始值
public static final int FIRST_SUB_WINDOW = 1000;
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
// 子窗口类型结束值,可以看出子窗口的 Type 值范围是 1000~1999。
public static final int LAST_SUB_WINDOW = 1999;

系统窗口

Toast、输入法窗口、系统音量条窗口、系统错误窗口都属于系统窗口。系统窗口的类型定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 系统窗口类型初始值
public static final int FIRST_SYSTEM_WINDOW = 2000;
// 系统状态栏窗口
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
// 搜索条窗口
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
// 通话窗口
@Deprecated
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
// 系统 ALERT 窗口
@Deprecated
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
// 锁屏窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
// TOAST 窗口
@Deprecated
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
...
// 系统窗口类型结束值,可以看出,系统窗口的 Type 值范围是 2000~2999。
public static final int LAST_SYSTEM_WINDOW = 2999;

窗口显示次序

当一个进程向 WMS 申请一个窗口时,WMS 会为窗口确定显示次序。为了方便窗口显示次序的管理,手机屏幕可以虚拟地用 X、Y、Z 轴来表示,其中 Z 轴垂直于屏幕,从屏幕内指向屏幕外,这样确定窗口显示次序也就是确定窗口在 Z 轴上的次序,这个次序称为 Z-Oder。Type 值是 Z-Oder 排序的依据,上面已知 Window 三大类型的 Type 值范围,在一般情况下,Type 值越大则 Z-Oder 排序越靠前,就越靠近用户。当然窗口显示次序的逻辑不会这么简单,情况会比较多,举个常见的情况;当多个窗口的 Type 值都是 TYPE_APPLICATION,这时 WMS 会结合各种情况给出最终的 Z-Oder。

Window 的标志

Window 的标志也就是 Flag,用于控制 Window 的显示,同样被定义在 WindowManager 的内部类 LayoutParams 中,一共有 20 多个,常用的如下:

设置 Window 的 Flag 有 3 种方法:

  • 通过 Window 的 addFlags 方法。

    1
    2
    3
    Window mWindow = getWindow();
    // addFlags 方法内部会调用 setFlags 方法。
    mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
  • 通过 Window 的 setFlags 方法。

    1
    2
    Window mWindow = getWindow();
    mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
  • 给 LayoutParams 设置 Flas,并通过 WindowManager 的 addView 方法进行添加。

    1
    2
    3
    4
    5
    WindowManager.LayoutParams mWindowLayoutParams = new WindowManager.LayoutParams();
    mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
    WindowManager mWindowManager = getSystemService(Context,WINDOW_SERVICE);
    TextView textView = new TextView(this);
    mWindowManager.addView(textView,mWindowLayoutParams);

软键盘相关模式

窗口和窗口的叠加是十分常见的场景,但如果其中的窗口是软键盘,可能会出现一些问题,比如典型的用户登录界面,默认的情况弹出的软键盘窗口可能会盖住输入框下方的按钮。为了使得软键盘窗口能够按照期望来显式,WindowManager 的静态内部类 LayoutParams 中定义了软键盘相关模式,常用的如下:

这里的 SoftInputMode 与 AndroidManifet 中 Activity 的属性 android:windowSoftInputMode 是对应的,并且,还可以在 Java 代码中为 Window 设置 SoftInputMode:getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);


Window 的操作

对于 Window 的操作,最终都是交由 WMS 来进行处理。窗口的操作分为两大部分,WindowManager 处理部分和 WMS 处理部分。Window 三大类型的窗口添加过程会有所不同,但是对于 WMS 处理部分,添加的过程基本一致。

接下来主要分析 Window 操作的 WindowManager 处理部分。

系统窗口的添加过程

系统窗口的添加过程也会根据不同的系统窗口有所区别,接下来以系统窗口 StatusBar 为例,它是 SystemUI 的重要组成部分,具体就是指系统状态栏,用于显示时间、电量和信号等信息。查看 StatusBar 的 addStatusBarWindow 方法,此方法负责为 StatusBar 添加 Window。StatusBar.java

1
2
3
4
5
6
7
8
private void addStatusBarWindow() {
// 用于构建 StatusBar 的视图
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
mRemoteInputController = new RemoteInputController(mHeadsUpManager);
// 调用了 StatusBarWindowManager 的 add 方法,并将 StatusBar 的视图(StatusBarWindowView)和 StatusBar 的高度传进去。
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}

StatusBarWindowManager 的 add 方法如下。StatusBarWindowManager.java

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
public void add(View statusBarView, int barHeight) {
// 通过创建 LayoutParams 来配置 StatusBar 视图的属性,包括 Width、Height、Type、Flag、Gravity、SoftInputMode 等。
mLp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
barHeight,
// 设置了 TYPE_STATUS_BAR,表示 StatusBar 视图的窗口类型是状态栏。
WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
PixelFormat.TRANSLUCENT);
mLp.token = new Binder();
mLp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
mLp.gravity = Gravity.TOP;
mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("StatusBar");
mLp.packageName = mContext.getPackageName();
mStatusBarView = statusBarView;
mBarHeight = barHeight;
// 调用了 WindowManager 的 addView 方法。
mWindowManager.addView(mStatusBarView, mLp);
mLpChanged = new WindowManager.LayoutParams();
mLpChanged.copyFrom(mLp);
}

WindowManager 的 addView 方法定义在 WindowManager 的父类接口 ViewManager 中,在 WindowManagerImpl 中实现。WindowManagerImpl.java

1
2
3
4
5
6
7
8
/**
* 第一个参数的类型为 View,说明窗口也是以 View 的形式存在的。
*/
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

调用了 WindowManagerGlobal 的 addView 方法。WindowManagerGlobal.java

首先要了解 WindowManagerGlobal 中维护的和 Window 操作相关的 3 个列表,在窗口的添加、更新和删除过程中都会涉及这 3 个列表,它们分别是 View 列表(ArrayList<View> mViews)、布局参数列表(ArrayList<WindowManager.LayoutParams> mParams )和 ViewRootImpl 列表(ArrayList<ViewRootImpl> mRoots )。

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
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {

...省略参数检查(首先会对参数 View、params 和 display 进行检查。)
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
// 如果当前窗口要作为子窗口,就会根据父窗口对子窗口的 WindowManager.LayoutParams 类型的 wparams 对象进行相应调整。
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
final Context context = view.getContext();
if (context != null&& (context.getApplicationInfo().flags& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
// 创建了 ViewRootImpl 并赋值给 root。
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 将添加的 View 保存到 View 列表中。
mViews.add(view);
// 将 root 存入到 ViewRootImpl 列表中。
mRoots.add(root);
// 将窗口的参数保存到布局参数列表中。
mParams.add(wparams);

try {
// 将窗口和窗口的参数通过 setView 方法设置到 ViewRootImpl 中,可见添加窗口这一操作是通过 ViewRootImpl 来进行的。
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}

ViewRootImpl 身负了很多职责,主要有以下几点:

  • View 树的根并管理 View 树。
  • 触发 View 的测量、布局和绘制。
  • 输入事件的中转站。
  • 管理 Surface。
  • 负责与 WMS 进行进程间通信。

接下来查看 ViewRootImpl 的 setView 方法。ViewRootImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// 调用了 mWindowSession 的 addToDisplay 的方法,mWindowSession 是 IWindowSession 类型的,它是一个 Binder 对象,用于进行进程间通信,IWindowSession 是 Client 端的代理,它的 Server 端的实现为 Session,此前的代码逻辑都是运行在本地进程的,而 Session 的 addToDisplay 方法则是运行在 WMS 所在的进程(SystemServer 进程)中。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
...
}

可以看出,本地进程的 ViewRootImpl 要想和 WMS 进行通信需要经过 Session,而 Session 为何包含在 WMS 中呢,查看 Session 的 addToDisplay 方法。Session.java

1
2
3
4
5
6
7
8
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
// 调用了 WMS 的addWindow 方法,并将自身也就是 Session 作为参数传了进去,每个应用程序进程都会对应一个 Session,WMS 会用 ArrayList 来保存这些 Session。
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

剩下的工作由 WMS 来处理,在 WMS 中会为这个添加的窗口分配 Surface,并确定窗口显示次序,可见负责显示界面的是画布 Surface,而不是窗口本身。WMS 会将它所管理的 Surface 交由 SurfaceFlinger 处理,SurfaceFlinger 会将这些 Surface 混合并绘制到屏幕上。

Activity 的添加过程

无论是哪种窗口,它的添加过程在 WMS 处理部分中是基本类似的,只不过会在权限和窗口显示次序等方面会有些不同。但是在 WindowManager 处理部分会有所不同,这里以最典型的应用程序窗口 Activity 为例,Activity 在启动过程中,如果 Activity 所在的进程不存在则会创建新的进程,创建新的进程之后就会运行代表主线程的实例 ActivityThread,ActivityThread 管理着当前应用程序进程的线程,这在 Activity 的启动过程中运用的很明显,当界面要与用户进行交互时,会调用 ActivityThread 的 handleResumeActivity 方法。ActivityThread.java

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
final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean                             reallyResume, int seq, String reason) {
...
// performResumeActivity 方法最终会调用 Activity 的 onResume 方法。
r = performResumeActivity(token, clearHide, reason);
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 得到 ViewManager 类型的 wm 对象。
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 调用了 ViewManager 的 addView 方法,此方法是在 WindowManagerImpl 中实现的,此后的过程与上面的系统窗口 StatusBar 的添加过程类似,唯一需要注意的是 ViewManager 的 addView 方法的第一个参数为 DecorView,这说明 Activity 窗口中会包含 DecorView。
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
...
}

Window 的更新过程

与 Window 的添加过程类似。需要调用 ViewManager 的 updateViewLayout 方法,updateViewLayout 在 WindowManagerImpl 中实现,WindowManagerImpl 的 updateViewLayout 方法会调用 WindowManagerGlobal 的 updateViewLayout 方法。WindowManagerGlobal.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
// 将更新的参数设置到 View 中。
view.setLayoutParams(wparams);

synchronized (mLock) {
// 得到要更新的窗口在 View 列表中的索引。
int index = findViewLocked(view, true);
// 在 ViewRootImpl 列表中根据索引得到窗口的 ViewRootImpl。
ViewRootImpl root = mRoots.get(index);
// 通过 remove 和 add 方法来更新布局参数列表。
mParams.remove(index);
mParams.add(index, wparams);
// 调用了 ViewRootImpl 的 setLayoutParams 方法将更新的参数设置到 ViewRootImpl 中。
root.setLayoutParams(wparams, false);
}
}

ViewRootImpl 的 setLayoutParams 方法在最后会调用 ViewRootImpl 的 scheduleTraversals 方法。ViewRootImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// Choreographer 译为 “舞蹈指导”,用于接收显示系统的 VSync 信号,在下一个帧渲染时控制执行一些操作。
// Choreographer 的 postCallback 方法用于发起添加回调,这个添加的回调将在下一帧被渲染时执行。
// 添加的回调指的是 TraversalRunnable 类型的 mTraversalRunnable。
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
1
2
3
4
5
6
7
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 此方法中又调用了 performTraversals 方法。
doTraversal();
}
}

performTraversals 方法使得 ViewTree 开始 View 的工作流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void performTraversals() {
...
// relayoutWindow 方法内部会调用 IWindowSession 的 relayout 方法来更新 Window 视图,最终会调用 WMS 的 relayoutWindow 方法。
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
// 内部会调用 View 的 measure 方法。
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
// 内部会调用 View 的 layout 方法。
performLayout(lp, mWidth, mHeight);
...
// 内部会调用 View 的 draw 方法。
performDraw();
...
}

在 ViewRootImpl 的 performTraversals 方法中分别调用了 performMeasure、performLayout 和 performDraw 方法,它们的内部又会调用 View 的 measure、layout 和 draw 方法,从而完成了 View 的工作流程。

在 performTraversals 方法中更新了 Window 视图,又执行 Window 中的 View 的工作流程,这样就完成了 Window 的更新。


Window 的内部机制

Window 是一个抽象的概念,每个 Window 都对应着一个 View 和 一个 ViewRootImpl,Window 和 View 是通过 ViewRootImpl 来建立联系的,因此 Window 并不是实际存在的,而是以 View 的形式存在。

Window 的添加过程

通过 WindowManager(一个接口,真正实现是 WindowManagerImpl 类) 的 addView 实现。
WindowManagerImpl 的工作模式是典型的桥接模式,将所有操作委托给 WindowManagerGlobal(以工厂的形式向外提供自己的实例) 来实现。
WindowManagerGlobal 的 addView() 中:

  • 检查参数是否合法,如果是子 Window 那么还需要调整一些布局参数。
  • 创建 ViewRootImpl 并将 View 添加到列表中。
  • 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程。

Window 的删除过程

也是先通过 WindowManagerImpl(removeView()) 后,再进一步 通过 WindowManagerGlobal 来实现的。
WindowManagerGlobal 的 removeView() 中:

  • 先通过 findViewLocked 来建立数组遍历查找待删除的 View 的索引,然后调用 removeViewLocked 做进一步删除。
  • removeViewLocked 通过 ViewRootImpl 的 die 方法来完成删除操作。
  • die 发送一个请求删除的消息后就立刻返回了(异步删除)。
  • ViewRootImpl 中 Handler 会处理此消息并调用 doDie 方法。
  • doDie() 内部会调用 dispatchDetachedFromWindow(),由它完成真正删除 View 的逻辑。

Window 的更新过程

WindowManagerGlobal 的 updateViewLayout():

  • 更新 View 的 LayoutParams 并替换掉 老的 LayoutParams。
  • 更新 ViewRootImpl 中的 LayoutParams。

Window 的创建过程

View 是 Android 中的视图的呈现方式,它必须附着在 Window 这个抽象的概念上面,因此有视图的地方就有 Window。

Activity 的 Window 创建过程

Activity 的启动过程最终由 ActivityThread 中的 performLaunchActivity() 来完成,这个方法内部会通过类加载器创建 Activity 的实例对象,并调用其 attach() 为其关联运行过程中所依赖的一系列上下文环境变量。

在 Activity 的 attach() 里,系统会创建 Activity 所属的 Window 对象并为其设置回调接口,Window 对象的创建是通过 PolicyManager(策略类,其真正实现是 Policy 类)的 makeNewWindow 方法实现的。由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接收到外界的状态改变时就会回调 Activity 的方法。

Activity 的视图是怎么附属在 Window 上的:
Activity 的视图由 setContentView() 提供,此方法中可看出,Activity 将具体实现交给了 Window(具体实现是 PhoneWindow)处理,PhoneWindow 的 setContentView() 大致遵循如下几个步骤:

  1. 如果没有 DecorView,那么就创建它
  2. 将 View 添加到 DecorView 的 mContentParent 中
  3. 回调 Activity 的 onContentChanged() 通知 Activity 视图已经发生改变

Dialog 的 Window 创建过程

过程与 Activity 类似:

  1. 创建 Window
  2. 初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中
  3. 将 DecorView 添加到 Window 中并显示

Toast 的 Window 创建过程

由于 Toast 具有定时取消这一功能,所以系统采用了 Handler。

Toast 内部有两类 IPC 过程:

  • Toast 访问 NotificationManagerService
  • NotificationManagerService 回调 Toast 里的 TN 接口

Toast 属于系统 Window,其内部视图由两种方式指定,它们都对应 Toast 的一个 View 类型的内部成员 mNextView:

  • 系统默认样式
  • 通过 setView() 指定一个自定义 View

备注

参考资料:
Android 开发艺术探索

Android 进阶解密

单词音标: