WindowManagerService简称WMS,它的功能很多很复杂。所以我们先介绍WindowManager体系,Window属性,Window的操作。
WMS主要是管理Window的。
WMS、Window、WindowManager的关系如下图:
Window是一个抽象概念,实体是View,WindowManager是用来管理Window的,WindowManager提供的功能最终都是在WMS中实现的。
WindowManager各种关系
WindowManager是一个接口,它继承了ViewManager接口,在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);
}
每个方法都是传入了View,说明Window就是以View的形式存在的。WindowManager还自己添加了很多和Window相关的方法。
Window的实现类其实是PhoneWindow,PhoneWindow是在Activity启动的时候,调用attach方法中创建的。
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*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
}
这里先创建了PhoneWindow,然后调用setWindowManager和WindowManager关联。下面看看setWindowManager方法的具体实现:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
setWindowManager(wm, appToken, appName, false);
}
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);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
可以看到最后是new了一个WindowManagerImpl出来,并将当前window传入了进去,这样WindowManagerImpl和Window又关联上了。
在WindowManagerImpl的addView等方法中,就会用到关联的Window,在该Window上添加View。
从WindowManagerImpl的代码可以看出,其实真正执行的是WindowManagerGlobal类,WindowManagerGlobal是一个单例,这样每一个进程就只有一个WindowManagerGlobal。
最终关系图如下:
- PhoneWindow继承了Window
- Window通过setWindowManager与WindowManager关联
- WindowManager继承ViewManager,WindowManagerImpl是WindowManager的实现类
- WindowManagerImpl具体功能是WindowManagerGlobal实现的
Window的属性
Window有很多属性,我们最常用的是Type(Window的类型)、Flag(Window的标志)、SoftInputMode(软键盘相关模式)。
Window类型
Window的类型有很多,应用程序窗口、系统错误窗口、输入法窗口、PopupWindow、Toast、Dialog等。
总的来说,Window的类型分为三大类,Application Window(应用程序窗口)、Sub Window(子窗口)、System Window(系统窗口),每一个类型下面又分了很多小的类型。他们都定义在WindowManager的LayoutParams中。
应用程序窗口
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;
public static final int LAST_APPLICATION_WINDOW = 99;
以上是应用程序窗口的类型,范围是1~99,这个数值的大小涉及到窗口的层级。
子窗口
子窗口需要依附在其他窗口才可以,PopupWindow就属于子窗口。
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;
public static final int LAST_SUB_WINDOW = 1999;
以上是子窗口,子窗口的范围是1000~1999
系统窗口
Toast、输入法、系统音量调条窗口、系统错误窗口都是系统窗口。
public static final int FIRST_SYSTEM_WINDOW = 2000;
...
//Toast
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
...
//系统错误
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
public static final int LAST_SYSTEM_WINDOW = 2999;
系统窗口的类型有很多,这里随便列了几个出来。系统窗口值是2000~2999。
窗口显示次序
确定窗口显示顺序就是在确定屏幕Z轴的顺序,值越大,排序就越靠前,就越在最上层。
Window的标志
Window的标志就是Window的Flag,用于控制Window的显示。
标志被定义在WindowManager的Layoutparams中,一共有20多个。
在给Window设置Falg的时候有三个方法
- 调用Window的addFlags方法
- 调用Window的setFlags方法
- 给LayoutParams设置Flag,当调用WindowManager的addView方法的时候传入
软键盘相关模式
窗口之间的叠加很常见,特别是软键盘出来的时候。软键盘出来有可能会挡住我们的输入框,我们就可以通过设置软键盘的相关模式来进行调整。
软键盘的模式也是定义在WindowManager的Layoutparams里面。
以上是常用的软键盘模式,软键盘的模式有两个设置的方法:
- 在AndroidManifest中通过windowSoftInputMode来设置。
- 直接给Window设置SoftInputMode
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
Window的操作
WindowManager对Window的操作就是添加、更新、删除的操作。这些操作最终都说交给WNS来操作的。所以窗口的操作分为WindowManager的处理部分和WMS的处理部分。
下面对添加系统窗口的流程做一个讲解。加入我们要添加StatusBar,首先会调用StatusBar的addStatusBarWindow这个方法:
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
mRemoteInputController = new RemoteInputController(mHeadsUpManager);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
添加StatusBar
这里先通过makeStatusBarView方法创建StatusBar,然后调用StatusBarWindowManager的add方法将创建好的StatusBar和StatusBar的高度传入。接下来我们看看add方法:
public void add(View statusBarView, int barHeight) {
mLp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
barHeight,
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;
mWindowManager.addView(mStatusBarView, mLp);
mLpChanged = new WindowManager.LayoutParams();
mLpChanged.copyFrom(mLp);
}
我们可以看到,这里先设置了Window的type,flag和softInputMode。最后调用了WindowManager的addView方法。这里最终调用的是WindowManagerGlobal的addView方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
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) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
这里先通过adjustLayoutParamsForSubWindow方法对LayoutParams进行调整,然后创建ViewRootImpl,并将创建好的ViewRootImpl添加到mRoots这个列表中。同时把view添加到mViews中,把wparams添加到mParams中。这里说一下,在WindowManagerGlobal中维护了三个列表mViews,mRoots,mParams,这三个列表在后面会讲到。
最后调用root的setView方法将View设置进去。所以最后添加是通过ViewRootImpl来完成的。
下面我们来看看ViewRootImpl中是怎么样的:
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
这里精简了很多代码,最主要的就是调用了IWindowSession的addToDisplay方法。IWindowSession是一个Binder对象,他的服务端实现为Session,Session的addToDisplay方法最终是运行在WMS中的。
所以本地的ViewRootImpl和WMS通信需要通过Session。
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
在Session的addToDisplay方法中,我们可以看到,调用了mService的addWindow方法,这里的mService就是WindowManagerService。并且将自己传入了进去。每一个应用程序都会对应一个Session,WMS用了一个集合mSessions来保存传入的Session。
接下来的工作就交给了WMS了,WMS会分配一个Surface给要添加的View,所以最终负责显示的是Surface,WMS管理的Surface交给SurfaceFlinger处理,SurfaceFlinger将这些Surface混合并绘制到屏幕上。
下面是添加StatusBar的流程图:
添加Activity
虽然在WMS中添加的操作都说差不多的,但是在WindowManager这边,不同级别的窗口还是有区别的。上面讲了添加系统级别窗口StatusBar,现在来讲讲应用程序窗口的添加。
说到应用窗口的添加,我们最常见的就是Acticity的添加,Activity在添加的时候会调用handleResumeActivity方法将Activity添加到界面上,下面我们来看看这个方法:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
}
...
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
...
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;
l.softInputMode |= forwardBit;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
...
}
上面精简了很多代码,我们可以看到先获得了ViewManager,然后又调用了addView方法。addView方法这里传入了DecorView,也就说明了Acticity里面是包含DecorView的。而这里Window的type设置的是TYPE_BASE_APPLICATION,也就是应用程序窗口。