前言
今天听到隔壁同事在面试校招生,两个人在那里对着 Window 侃侃而谈。说实话,工作中除了设置一些 Window 的属性调整界面显示之外,几乎没怎么用到 Window,既然如此就挖一下 Window 这里,加深一下对这里的理解吧~~~
1. Window 是啥东西?
作为 Android 程序猿,多多少少都了解 Window 是什么,就算没深入了解过,肯定也听过这个概念。
顾名思义,Window 就是窗口,可以理解为一个容器,承载着各种各样的 View。Window 其实只是一个抽象概念,并不是真实存在的。就像一个班集体,因为有了班级里的同学,集体这个概念才存在。Window 也是一样的,它的实体表现其实就是 View。
Window 是一个抽象类,它的唯一实现类是 PhoneWindow。PhoneWindow 里面呢,有一个非常重要的逻辑就是创建 DecorView 对象。DecorView 也并不神秘,就是 FrameLayout 的子类,换言之就是 View 的子类。PhoneWindow 之所以要创建 DecorView 对象,目的就是要把它作为视图树的根 View。当创建 Activity 的时候内部就会通过 PhoneWindow 来创建 DecorView。
总而言之,Window 既然是 View 的容器,肯定是要加载各种各样的 View 进来的,而 DecorView 就是这些 View 的根 View。
如果你还想多了解 DecorView,我之前有写过一篇文章 View 系列 —— 小白也能看懂的 DecorView 没事的话可以去瞅瞅。
2. 既然 Window 并不是真实存在的,实际上是以 View 的形式存在,那么为什么还需要 Window 这个概念呢?
想象一下,如果没有 Window 这个容器,多个 View 在显示时谁在前面,谁在后面?如果我想在顶层显示一个 View,比如弹出一个 dialog,又该如何控制呢?Window 就是起到控制 View 的作用。总而言之,Window 是 View 的管理者,同时也是 View 的载体,而 View 是 Window 的表现形式。
3. 明白了 Window 是什么,那它是啥时候被创建的呢?
至于 Window 被创建的时机,当 Activity 被创建的时候就会去创建一个 Window。可以看看下面的 Activity 创建时的源码:
// ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
try {
...
Window window = null;
...
// 1.Activity的attach方法中会创建window
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken, r.shareableActivityToken);
}
..
return activity;
}
// Activity.java
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, IBinder assistToken,
IBinder shareableActivityToken) {
...
// 2.创建Window
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
注释 1 处,在创建 Activity 的时候会通过 attach() 方法来创建 Window。注释 2 处,可以看到,attach() 中初始化了 PhoneWindow,前面说过 Window 的唯一实现类就是 PhoneWindow,所以很显然,attach() 中就是创建 Window 的地方。
4. 既然 Window 是 View 的容器,那么 Window 是怎么加载 View 的?
以 DecorView 为例,既然 DecorView 在 Activity 创建的时候去初始化,那它又是什么时候被加载到 Window 中来显示的呢?
揭晓答案,当处理 Activity 的 Resume 时,会将 DecorView 加载到 Window 中。看下源码:
// ActivityThread.java
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
...
// 1.performResumeActivity 回调用 Activity 的 onResume
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
...
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
// 2.获取 decorview
View decor = r.window.getDecorView();
// 3.decor 现在还不可见
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 4.decor 添加到 WindowManger中
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
...
}
上面的源码很清晰,当获取到当前 Window 的根 View DecorView 后,创建了 WindowManager,用 WindowManager 的 addView() 方法把 DecorView 给加载进去。
5. 看来主要的加载逻辑都在 WindowManager 中,那就说说 WindowManager 吧
看名字 WindowManager 就是一个 Window 管理器,继承自 ViewManager。由于 Window 是一个抽象概念,使用中我们并不会直接操作 Window,而是通过其管理类 WindowManager 来实现添加删除等操作。
public interface ViewManager
{
// 添加 View
public void addView(View view, ViewGroup.LayoutParams params);
// 更新 View
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
// 删除 View
public void removeView(View view);
}
ViewManager 的主要方法就这三个,分别用来添加、更新和删除 View。从这里的方法中传入的参数都是 View,也能看出 Window 的是以 View 的形式存在的。
WindowManager 这里的逻辑就有点绕了,它有一个自己的实现类 WindowMangerImpl,WindowMangerImpl 又把添加、更新、删除 View 的操作委托给 WindowMangerGlobal 来完成。
// WindowMangerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
// 实际上是通过 WindowManagerGlobal 完成 addView 的操作的,删除和更新 View 也是一样的
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
6. 先从 addView 开始,看看 WindowManagerGlobal 中是如何处理的?
addView() 的代码挺长,源码嘛,我们看重要部分就行啦。我拣选了一下主要的代码:
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
...
ViewRootImpl root;
View panelParentView = null;
// 1.同步方法
synchronized (mLock) {
...
// 2. 创建ViewrootImpl的对象
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// View列表
mViews.add(view);
// ViewRootImpl列表
mRoots.add(root);
// 布局参数
mParams.add(wparams);
try {
// 3.将View与ViewRootImpl对象进行关联
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
首先从 addView 方法的第一个参数是 View 也说明了 Window 是以 View 的形式存在的。该方法中维护了 3 个列表,分别是 View 列表、ViewRootImpl 列表和布局参数列表。在后面 removeView 方法中会相应的从列表中移除对应对象。
注释 1:WindowManagerGlobal 的 addView() 方法是一个同步方法,使用了 synchronized 关键字来保证线程安全。这是因为在多线程环境下,可能会有多个线程同时向 WindowManager 中添加 View,如果不进行同步,可能会导致 View 的显示出现问题。
注释 2:在 addView 方法中,首先会创建一个 ViewRootImpl 对象,然后将该对象存放到 mRoots 这个列表中。
注释 3:用 ViewRootImpl 的 setView 方法,将 View 与该 ViewRootImpl 对象进行关联。setView 这个方法也很老生常谈了。了解 View 的工作流程的都知道 setView 里面最重要的方法就是 requestLayout() 了,里面会调用到 performTraversals() 方法开启 View 的三大工作流程。但是除此之外,setView 中还涉及到添加 Window 的操作。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
...
requestLayout();
...
try {
...
// 添加 Window
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
...
}
} catch (RemoteException e) {
...
}
}
}
}
添加 Window 的过程就是通过 mWindowSession 来完成的,该变量是 IWindowSession 类型,是一个 AIDL 文件,很显然 Window 的添加过程其实是一次 IPC 进程通信的过程。调用的实际是 Session 类的 addToDisplayAsUser。在 addToDisplayAsUser 中最终是通过 WindowManagerService 的 addWindow 方法实现了 Window 的添加。
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
// 最终使用WindowManagerService来添加View
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
outAttachedFrame, outSizeCompatScale);
}
总结
Window 调用 addView 方法时,实际上委托给了 WindowManagerGlobal 来处理,为了保证线程安全,addView 是同步方法。在方法中会创建一个 ViewRootImpl 对象,并调用其 setView 方法用来把 View 添加到 Window 中。setView 的内部涉及到了进程通信,最终会通过 WindowManagerService 的 addWindow 方法实现了 View 的添加。
7. removeView()
话不多说,先看看 removeView() 的源码:
// WindowManagerGlobal.java
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
// 1.找到要删除的View的index
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
// 2.删除操作
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
删除方法看起来很直观,先通过 findViewLocked 方法找到需要删除的 View 的索引,然后调用 removeViewLocked 进行删除,这里的入参 immediate 表示立即删除,也就是同步删除。若为 false,表示可以异步删除。
// WindowManagerGlobal.java
private void removeViewLocked(int index, boolean immediate) {
// 在addView时会把ViewRootImpl对象存储在列表中,根据索引将其取出
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (root != null) {
root.getImeFocusController().onWindowDismissed();
}
// ViewRootImpl执行die()
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
在 removeViewLocked 方法中,首先获取要删除的 index 所对应的 ViewRootImpl 对象。然后执行其 die 方法:
// ViewRootImpl.java
boolean die(boolean immediate) {
// 立即删除
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
...
// 不用立即删除的话,就往消息队列发送个消息
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
die 方法中会对 immediate 进行判断,如果为 true,需要立即执行删除操作 doDie(),否则会发送一个 MSG_DIE 的消息,等 ViewRootImpl 的 Handler 处理该消息并调用 doDie()。
void doDie() {
// ViewRootImpl.java
checkThread();
synchronized (this) {
// 1. 是否已经执行过doDie方法了,执行过就不再执行了
if (mRemoved) {
return;
}
mRemoved = true;
// 2. 如果该View已经添加到Window中了,执行dispatchDetachedFromWindow进行删除
if (mAdded) {
dispatchDetachedFromWindow();
}
...
mInsetsController.onControlsChanged(null);
// 3. 清理与View相关的状态
mAdded = false;
}
// 4. 执行一些WindowManagerGlobal中与要删除的View相关的参数。
WindowManagerGlobal.getInstance().doRemoveView(this);
}
根据代码中的分析,可以看到 doDie() 首先会对与该 View 相关的一些参数进行判断和设置,删除的逻辑在 dispatchDetachedFromWindow 中,最后执行 doRemoveView 删除的View相关的参数。下面我们先看一下 dispatchDetachedFromWindow 方法:
void dispatchDetachedFromWindow() {
...
if (mView != null && mView.mAttachInfo != null) {
// 1. 调用View的dispatchDetachedFromWindow
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
// 2. 各种资源回收、移除监听的操作。
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
setAccessibilityFocus(null, null);
mInsetsController.cancelExistingAnimations();
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
destroySurface();
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
// 3. 删除Window的地方
try {
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
...
}
在这个方法中,会调用 View 的 dispatchDetachedFromWindow 方法,给一个回调,代表 View 被移除了。然后执行资源回收和移除监听的操作。再通过 mWindowSession 进行 IPC 操作调用 WindowManagerService 的 removeWindow() 移除 View。
总结
WindowManagerGlobal 的 removeView 中首先会去定位到要删除 View 的 index,根据这个 index 我们就能在列表中获取到对应的 ViewRootImpl 对象,并执行 die()。doDie() 可以选择是否要立即删除,立即删除就会直接执行 doDie() 方法,否则的话就使用 Handler 往消息队列发送消息等待执行。无论是否立即执行,都是会调用 doDie()。具体的删除逻辑是在 dispatchDetachedFromWindow 方法中,同样是通过进程通信,使用 WindowManagerService 的 removeWindow() 移除 View 的。
8. updateViewLayout()
最后看看 Window 是如何更新 View 的:
// WindowManagerGlobal.java
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.setLayoutParams(wparams);
synchronized (mLock) {
// 获取View的index
int index = findViewLocked(view, true);
// 根据index得到ViewRootImpl对象
ViewRootImpl root = mRoots.get(index);
// 移除旧布局参数
mParams.remove(index);
// 添加新布局参数
mParams.add(index, wparams);
// 把新布局设置给View
root.setLayoutParams(wparams, false);
}
}
更新操作比较简单,和 remove 方法一样,都是要通过 View 的 index 来得到 ViewRootImpl 对象。然后会更新 View 的 LayoutParams 并替换掉老的。接着调用 setLayoutParams 方法把新的布局参数设置给 View。
// ViewRootImpl.java
public void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
...
// Window更新View的时候,newView是false
if (newView) {
mSoftInputMode = attrs.softInputMode;
requestLayout();
}
...
// 最终会执行scheduleTraversals
scheduleTraversals();
}
}
setLayoutParams 方法最后会执行到我们熟悉的 scheduleTraversals() 方法中,最后通过 performTraversals() 开启 View 的三大工作流程。不了解 scheduleTraversals() 和 performTraversals() 的可以看看这篇文章 终于搞明白了什么是同步屏障。
更新 View 也同样会进行进程通信,在 performTraversals() 中会执行 relayoutWindow()
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
...
int relayoutResult = mWindowSession.relayout(mWindow, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize);
...
同样是通过 mWindowSession 进行 IPC,最终通过 WindowManagerService的 relayoutWindow() 来实现。
总结
OK,讲了这么多,我们最后就来捋一捋与 Window 相关的类之间的关系:
-
Window:Window 作为 View 的载体是一个抽象概念,其实体是 View。
-
WindowManager:接口,提供了创建、更新和删除 Window 等方法。实际上,应用程序通过 WindowManager 调用的接口最终都会传递给 WMS 来执行。
-
ViewRootImpl:在调用 WindowManager 的 addView() 时,会给这个 View 树分配一个ViewRootImpl,ViewRootImpl 负责与其他服务(WMS)联络并指导 View 树的绘制工作。
-
WindowManagerService(WMS):系统级服务,负责管理 Window 的创建、显示、更新和销毁。它是Android系统中窗口管理的核心,最终都是通过 IPC 来调用 WMS 中的方法来管理 Window 的。