前语
今日听到隔壁搭档在面试校招生,两个人在那里对着 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 的。
参阅
直面底层:这一次,彻底搞懂Android 中的Window
捋一捋,到底怎么样去了解Window机制?