WMS、WindowManager与Window之间的联系
- Window是一个抽象类,在Andorid系统中其仅有一个具体实现类即PhoneWindow,负责对View进行管理。
- WindowManager是一个接口类,继承自接口ViewManager,它的实现类为WindowManagerImpl。顾名思义其是用来管理Window的,负责Window的添加、更新和删除操作。
- WindowManager通过Binder与WindowManagerService进行跨进程通信,把具体的实现工作交给WindowManagerService来完成。
简单的理解就是:Window是View的载体,通过WindowManager实现对Window增删改的行为,具体的实现操作由WindowManager通过Binder方式交由WMS去处理。
WindowManager是如何与Window进行关联的?
由于在Activity的构建过程中会创建与Acitvity相对应的Window,所以下面我们从Activity的attach方法着手,一步步深入源码来查看Window是如何与WindowManager建立联系的。
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);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
//②给Window设置WindowManager
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);
}
我给上述代码中的两个关键位置加上了中文注释:
①mWindow = new PhoneWindow(this, window, activityConfigCallback);
前面我们解释过,PhoneWindow是Android中唯一一个实现了Window的类,所以此处通过实例化PhoneWindow的方式给Activity的mWindow变量赋值。② mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
此处即是通过调用Window的setWindowManager方法建立Window和WindowManager之间的联系。
接着看setWindowManager方法的具体实现
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) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//Window的实际管理者为WindowManagerImpl
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
该方法会先判断传来的WindowManager是否为null。若为null的话,会再次获取注册在系统中的Window服务,然后将WindowManager向下转型为WindowManagerImpl对象并调用其createLocalWindowManager方法,给WindowManagerImpl中的mParentWindow变量赋值。
至此,Window中的mWindowManager变量持有WindowManagerImpl对象,WindowManagerImpl中的mParentWindow变量持有对应的PhoneWindow对象,二者进行了相互关联。
WindowManager是如何对Window进行操作的?
ViewManager接口只有三个抽象方法
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对Window的操作可以理解为就是对View的操作,即View的添加、更新以及删除。
Tip:这里就存在一个问题了,既然WindowManager的操作都是面向View的,那Window的存在还有什么意义?
答:Window的类型有很多种,比如应用程序窗口、系统错误窗口、输入法窗口、PopupWindow、Toast、Dialog等。总的来说Window按功能划分可以分为三大类,分别是应用程序窗口、子窗口和系统窗口。每个大类型中又包含了很多种类型,它们的存在很好的满足Andorid系统中各式各样的功能对不同窗口的需求。起主要作用的有三种属性:Type(主要用来管理窗口的显示次序),Flag(用于控制Window的显示(如全屏、保持常亮等))和SoftInputMode(控制软键盘的显示行为)。
下面就以addView为例来详细剖析WindowManager是如何对Window进行添加操作的。关于addView方法的具体实现我们要在WindowManager的实现类WindowManagerImpl中查看。
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);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
可以清晰的看到,WindowManagerImpl的操作全部都委托给了WindowManagerGlobal去处理,这里用到的是桥接模式。而WindowManagerGlobal是通过单例模式获取到的,也就是说一个进程中只存在一个WindowManagerGlobal实例。
下面继续看WindowManagerGlobal的addView方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
View panelParentView = null;
......
//①实例化一个ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//②将View、ViewRoot以及View的参数三者放入WindowManagerGlobal的数组中保存
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//关键代码!!!!
//③将添加View的操作交至ViewRoot处理
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
上述代码有三个关键点,①实例化一个ViewRootImpl对象;②将View、布局参数、ViewRoot三者添加进WindowManagerGlobal所维护的数组中;③将添加View的操作交由ViewRoot继续处理
下面继续看ViewRootImpl的setView方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
......
// 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();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//
//通过IPC方式添加视图
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
......
}
}
上述代码首先调用requestLayout方法,该方法最终会调用performTraversals来开始第一次视图绘制,具体的绘制过程参考另一篇文章。随后,便是调用mWindowSession的addToDisplay方法以IPC的方式将添加的视图传递到WMS。
其中mWindow是IWindow.Stub的子类所构造的对象,其内部持有ViewRootImpl的弱引用。
下图以系统窗口StatusBar为例,展示了视图的添加流程。
WMS是如何对Window进行操作的?
上述过程WindowManager将添加Window的操作通过ViewRootImpl中所持有的IWindowSession以Binder的形式传递到了WMS进程。由于WMS较为复杂,下面我们就简要的描述一下WMS针对addWindow这个方法做了哪些事。
- ①WMS对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会再执行后面的代码逻辑。
- ②WindowToken相关的处理,比如有的窗口类型需要提供WindowToken,没有提供的话就不会执行后面的代码逻辑,有的窗口类型则由WMS隐式创建WindowToken。(每个Activity都对应一个WindowToken)
- ③WindowState的创建和相关处理,将WindowToken和WindowState相关联。
- ④创建和配置DisplayContent,完成窗口添加到系统前的准备工作。