捋一捋,到底怎么样去理解Window机制?
作者:
d袋鼠b
, 链接:https://juejin.cn/post/6952341892721803294
说到 Window 机制,通常想到的就是PhoneWindow
、ViewRootImpl
、WindowManagerImpl
、子窗口
、DecorView
等等,网上也有不少博客通过源码分析他们之间的调用关系,可是能说得比较清楚的却不多,或深入源码不可自拔,或越说越复杂概念一大堆。
今天,我们就来好好捋一捋,到底怎么样去理解Window机制呢?
先撇开复杂源码与难懂的概念,我们现在谈的Window都是单纯地指Window这个类,请读者暂时抛开"窗口"、“子窗口”、 “子Window” 这些扰乱思路的概念,文章后面也会对这些概念以及应用作出说明和解释,所以先看看Window这个类,由简单到复杂。(" 子Window" 可以视为一个错误的说法,这词在对理解Window机制上没什么好处,所以请永远忘记它!!!) 为了有更好的阅读思路,这里先给出几条结论:
每个Window都有自己的唯一的一个 WindowManager
,WindowManager
负责维护它所在的Window里面的内容。在Android平台上Window的具体实现是 PhoneWindow
、对应它的WindowManager
是WindowManagerImpl
。WindowManagerImpl
维护PhoneWindow
中的什么内容呢? 答案:DecorView及其子View所组成的View树。一个 Activity
有且只有一个PhoneWindow
实例,一个Dialog同样有且只有一个PhoneWindow
实例。
可能聪明的同学就会问了: 怎么没看到你的理解中有ViewRootImpl、WindowManagerGlobal这些呢?我给张图你就明白了。
如上图:
一个
Window
都有一个WindowManager
负责管理它的内容。PhoneWindow
的内容则交给WindowManagerImpl
管理,其中的“内容”指的则是DecorView
及其子View所组成的View树。当
WindowManagerImpl
决定管理View树时,会给这个View树分配一个ViewRootImpl
,ViewRootImpl则是负责与其他服务联络并指导View树的绘制工作。Activity
有PhoneWindow
对象,Dialog
同样也有自己的PhoneWindow
对象
Window与WindowManager
虽然在 Android 平台上PhoneWindow
是Window
的唯一实现,可是直接跳过Window直接看PhoneWindow这种做法并不值得提倡。
打开Window
的源码,会发现它有一个WindowManager
对象。这个WindowManager
就负责管理当前Window对象的内容。
//: Window.java
public abstract class Window {
...
private WindowManager mWindowManager;
...
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
}
所以,不管Window的实现类是不是PhoneWindow
,它都会有一个它自己的管理者WindowManager
管理它的内部。
Activity 与 PhoneWindow
接下来再看看Window的唯一实现PhoneWindow
。在 Android
中脱离Activity
谈PhoneWindow
都是耍流氓。
所以这里将主要介绍Actitity
启动过程中,PhoneWinow
的创建流程、WindowManagerImpl
的绑定、DecorView
的创建以及ViewRootImpl
的分配。
创建PhoneWindow
首先一起看一下PhoneWindow
是何时创建的。众所周知,Activity
启动时 ActivityThread
会调用 performLaunchActivity()
方法创建一个Activity实例,紧接着会调用它的 attach 方法。
//: ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
...
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
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);
}
在 Activity
的 attach
方法当中,PhoneWindow
被创建并赋值给 Activity
的成员变量mWindow
,紧接着会为PhoneWindow
设置一个WindowManager
。
//: 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) {
...
//创建PhoneWindow对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
//为PhoneWindow设置WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
}
//: Window.java
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//创建WindowManagerImpl实例,并赋值给Window的成员变量mWindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
//: WindowManagerImpl.java
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
可以看到Activity
并不是天生就有PhoneWindow
的,只是在启动过程中Activity
的attch()
方法被调用,然后才会给Activity
创建PhoneWindow
对象,并在PhoneWindow
创建后紧接着给它新建一个WindowManagerImpl
。
attch()
完成后,Activity
就是这个样子了。
创建DecorView
当PhoneWindow
及其内容管理者WindowManagerImpl
创建好了以后,就需要关心管理者所管理的View树是何时创建了。也就是DecorView
何时创建,这一点大部分读者都比较熟悉,在 Activity
中调用 setContentView()
时,DecorView
会被创建。
//: Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
上述的 getWindow().setContentView(layoutResID)
实际调用的就是PhoneWindow
的setContentView
方法。其源码如下:
//: PhoneWondow.java
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
//创建DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//创建DecorView并赋值给PhoneWindow的成员变量mDecor
mDecor = generateDecor(-1);
...
}
...
}
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, this);
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
//新建DecorView对象,并将PhoneWindow实例传入其中
return new DecorView(context, featureId, this, getAttributes());
}
完成setContent
后,PhoneWindow
也有自己的DecorView了。不过到目前为止,虽然Activity
有PhoneWindow
,有PhoneWindow
也有WindowManagerImpl
和DecorView
(或View树)了,此时的 Activity仍然不会在界面上显示任何东西。
要在界面上显示View树,必须要通知WindowManagerServices
才可以,这个通知工作在是在ViewRootImpl
之中完成,然而目前还未曾创建ViewRootImpl
。
创建ViewRootImpl
上面讲到在View树显示到界面之前,需要有一个ViewRootImpl
负责指导它的绘制显示工作。那这个ViewRootImpl
是何时创建并与View树关联的呢?
一句话简要概况:当WindowManagerImpl决定管理View树时,会给它关联一个ViewRootImpl实例。
从代码层面来说就是WindowManagerImpl
调用addView
方法将View树添加到View列表中时。
先看一下WindowManagerImpl
的addView
方法:
//: WindowManagerImpl.java
//单例 WindowManagerGlobal
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);
}
可以看到所有的WindowManagerImpl
对View的管理都是交给一个唯一的WindowManagerGlobal
了。所以,我们需要再看看WindowManagerGlobal
的addView
方法:
//: WindowManagerGlobal.java
private static IWindowSession sWindowSession;
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
// 创建ViewRootImpl实例
root = new ViewRootImpl(view.getContext(), display);
// 给View设置属性
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
//将ViewRootImp于View进行绑定
root.setView(view, wparams, panelParentView);
...
}
}
可以看到当WindowManagerGlobal
的addView
方法被调用时,ViewRootImpl
被创建并将其与View树进行绑定。ViewRootImpl
与View树进行绑定后,Activity看起来就是这个样子了。
看到这里又有同学要举手提问了,这个Activity何时会让WindowManagerImpl
执行addView
操作呢?
为了解决这个问题?我们需要再回到Activity的启动流程,观察ActivityThread
的handleResumeActivity
方法:
//: ActivityThread.java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
// TODO Push resumeArgs into the activity for consideration
//内部会调用Activity的onResume方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
final Activity a = r.activity;
final int forwardBit = isForward
? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityTaskManager.getService().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
//从willBeVisible的注释中可以了解到当窗口没有被添加到WindowManager中时willBeVisible为true
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//窗口类型设置
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//WindowManagerImpl将View添加到自己的管理队列中
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
}
阅读完handleResumeActivity
方法后,WindowManagerImpl
的addView
方法正式在此方法中完成的。
流程有点长,先小结一下之前的内容:
第一步:在Activity的启动过程中,
ActivityThread
会创建一个Activity实例。第二步:在Activity实例创建完成后,紧接着执行
Activity#attach()
方法。第三步:在
Activity#attach()
方法之内PhoneWindow被创建,并同时创建一个WindowManagerImpl
负责维护PhoneWindow内的内容。第四部:在
Activity#onCreate()
中调用setContent()
方法,这个方法内部创建一个DecorView实例作为PhoneWindow的内容。第五步:在
ActivityThread#handleResumeActivity()
方法中WindowManagerImpl#addView
被调用,WindowManagerImpl
决定管理DecorView
,并创建一个ViewRootImpl
实例,将ViewRootImpl
与View树进行关联,这样ViewRootImpl就可以指挥View树的具体工作。
通过后面的学习可以了解到,ViewRootImpl
指挥View树的这些工作包含:View树的显示、测量绘制、同步刷新以及事件分发。
惊不惊喜,意不意外???ViewRootImpl原来这么重要,传说的众View之父呀!
仔细想一下,就知道其实没什么意外的~~~
ViewRootImpl与View
刚刚我们交代了ViewRootImpl
的创建时机,同时也讲到View树的显示、测量绘制、同步刷新以及事件分发这些工作与ViewRootImpl
联系紧密。
ViewRootImpl
再看看它的具体实现:
//: ViewRootImpl.java
final IWindowSession mWindowSession;
final W mWindow;
Choreographer mChoreographer;
WindowInputEventReceiver mInputEventReceiver;
public ViewRootImpl(Context context, Display display) {
...
mWindowSession = WindowManagerGlobal.getWindowSession();
mWindow = new W(this);
mChoreographer = Choreographer.getInstance();
...
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
......
// 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.
//WMS添加窗口之前,进行一次测量布局工作,这样才能保证各类系统事件与View位置对应关系的准确性
requestLayout();
//通过mWindowSession通知WMS添加并显示窗口
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
......
//初始化事件接收器,
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
//将当前ViewRootImpl实例赋值给DecorView的mParent变量
//众View之父,由此而来
view.assignParent(this);
}
ViewRootImpl
中有mIWindowSession
、mWindow
、mChoreographer
、mInputEventReceiver
三个比较重要的变量。
mIWindowSession与mWindow
mIWindowSession
、mWindow为Binder对象,用于APP端与WMS之间的相互通信。细心的读者可能会发现,在ViewRootImpl
对象创建后紧接着有一个ViewRootImpl
的setView
操作。不难发现,在ViewRootImpl#setView
中使用了IWindowSession.addToDisplay
来通知WMS添加并显示窗口。对WMS添加窗口细节感兴趣的读者可以参看《WindowManagerService窗口管理之Window添加流程》
关于APP端使用mIWindowSession
与mWindow
与WMS
通讯细节的问题,可以参考文章:
https://blog.csdn.net/yangwen123/article/details/18733631
mChoreographer
对于部分同学来说Choreographer
可能并不陌生,我们通常叫做“编舞者”。无论是系统同步刷新、View的requestLayout
还是界面帧率监控,都能看到Choreographer
的身影。
Choreographer要是展开讲的话,内容太长。希望读者可以去看看袁辉辉大佬的《Choreographer原理》
,或者也可以看看这篇《Android 怎么就不卡了呢之Choreographer》
或者《你真的了解16.6ms刷新机制吗?》
mInputEventReceiver
最后一点要讲的就是WindowInputEventReceiver
在事件分发事件中的作用。可能一谈到事件分发,部分同学就会想到View的事件分发,可是View的事件是谁传给它的呢?由于笔者水平有限,强烈推荐袁辉辉大佬的《Input系统—事件处理全过程》,如果觉得难度太大的话可以先看看《原来Android触控机制竟是这样的?》。
子窗口
一说到Window机制,不少同学就喜欢聊子窗口,可是我却不喜欢聊它。为啥?在我看来这个概念太务虚了,就像去了解“茴香豆”的“茴”字有几种写法是一个道理,咬文嚼字不值得提倡。另外一点,它干扰了我们理解Window机制。
首先,什么是窗口?警告:一个窗口本质上是一个View树,就像Activity中DecorView及其子View组成的一颗View树,就可以视作是Activity内的一个窗口,注意它并不是Window对象。
之前我们讲到Activity启动过程中,会创建一个PhoneWindow与对应的WindowManagerImpl
对象,然后在setContent
的时候会给PhoneWindow
创建一个DecorView
,最终在WindowManagerImpl#addView
中给DecorView分配一个ViewRootImpl
对象负责指导DecorView的工作。这个DecorView及其子View组成的View树就是一个窗口。它的窗口类型为WindowManager.LayoutParams.TYPE_BASE_APPLICATION(在ActivityThread#handleResumeActivity()
方法中被设置的),所以我们把它叫做应用窗口。
那什么是子窗口?
在PhoneWindow与WindowManagerImpl
创建好了以后,我们自己也可以调用WindowManagerImpl#addView
来添加一个View树,也叫添加窗口。当它的窗口类型处于WindowManager.LayoutParams.FIRST_SUB_WINDOW
与WindowManager.LayoutParams.LAST_SUB_WINDOW
之间时,我们称这个直接添加的窗口为子窗口。
当然,也有的同学把直接调用WindowManagerImpl#addView
创建的窗口都叫做子窗口。本文并未采取这种方式定义子窗口这一概念。
那这些直接通过WindowManagerImpl#addView
创建的窗口公用同一个PhoneWindow以及WindowManagerImpl
对象,窗口类型不同决定了它们在WMS上的一个z-order顺序。
PopupWindow
接下来让我们看看子窗口是如何添加的,以PopupWindow为例:
//: PopupWindow.java
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
public void showAtLocation(View parent, int gravity, int x, int y) {
mParentRootView = new WeakReference<>(parent.getRootView());
//这个getWindowToken返回的就是ViewRootImpl里的mWindow对象
showAtLocation(parent.getWindowToken(), gravity, x, y);
}
public void showAtLocation(IBinder token, int gravity, int x, int y) {
if (isShowing() || mContentView == null) {
return;
}
detachFromAnchor();
mIsShowing = true;
mIsDropdown = false;
mGravity = gravity;
//创建窗口属性,配置窗口类型
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);
p.x = x;
p.y = y;
invokePopup(p);
}
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType;
p.token = token;
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
return p;
}
//添加窗口
private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
//添加窗口
mWindowManager.addView(decorView, p);
if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
可以看到PopupWindow通过WindowManagerImpl
直接添加了一个类型为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
类型的窗口。
这个mWindowManager
就是Activity
的WindowManager
,也就是Activity内PhoneWindow
的WindowManagerImpl
,所以,这个窗口要依附于Activity。Application没有WindowManager
所以不能被依附。
另一方面,这个窗口类型恰好是一个FIRST_SUB_WINDOW类型,所以PopupWindow是一个真正的子窗口。
//: WindowManager.java
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;`
Dialog
看完了PopupWindow再来看看Dialog有何不同?
//: Dialog.java
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//创建PhoneWindow
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 show() {
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
......
onStart();
mDecor = mWindow.getDecorView();
WindowManager.LayoutParams l = mWindow.getAttributes();
//添加窗口
mWindowManager.addView(mDecor, l);
}
可以看到,Dialog与PopupWindow
不同,它有自己的PhoneWindow对象,同时Dialog的窗口类型为TYPE_APPLICATION
,所以不能视为一个子窗口。
总结
文章篇幅较长,最后我们再总结一下。
无论是Activity
还是PopupWindow
,又或者Dialog。这些对象需要显示到界面,都是通过调用WindowManagerImpl#addView
来间接完成,它们所显示的内容实际就是View树,这个View树我们称之为“窗口”。addView
时,会生成一个ViewRootImpl
与View树进行关联,ViewRootImpl
内部则通过WindowSession
来与WMS进行通讯最终完成显示。另一方面,WMS通过一个ViewRootImpl$W实例(mWindow)
代理,将WMS端的事件传递到ViewRootImpl
,最终交给View树。
参考
Choreographer原理 原来Android触控机制竟是这样的? Input系统—事件处理全过程 Android 怎么就不卡了呢之Choreographer Android 应用程序建立与WMS服务之间的通信过程 Android事件分发机制前篇——事件如何传递到Activity中 你真的了解16.6ms刷新机制吗?
---END---
更文不易,点个“在看”支持一下👇