WindowManagerService(以下简称WMS)是做什么的?
打个比方,就像一出由N个演员参与的话剧:SurfaceFlinger是摄像机,WMS是导演,ViewRoot则是演员个体。摄像机(SurfaceFlinger)的作用是单一而规范的——它负责客观地捕获当前的画面,然后真实地呈现给观众;导演(WMS)则会考虑到话剧的舞台效果和视觉美感,如他需要根据实际情况来安排各个演员的排序站位,谁在前谁在后,都会影响到演出的“画面效果”与“剧情编排”;而各个演员的长相和表情(ViewRoot),则更多地取决于他们自身的条件与努力。正是通过这三者的“各司其职”,才能最终为观众呈现出一场美妙绝伦的“视觉盛宴”。
从计算机I/O系统的角度来分析,WMS至少要完成以下两个功能。
根据计算机体系结构的分类,“窗口管理”属于“输出”部分——应用程序的显示请求在SurfaceFlinger和WMS的协助下有序地输出给物理屏幕或者其他显示设备。
与此相对应,“事件的管理派发”就可以看作WMS的“输入”功能。这同时也是WMS区别于SurfaceFlinger的一个重要因素——因为后者只做与“显示”相关的事情,而WMS则还要“兼职”对输入事件的派发。
WMS属于SystemServer启动众多系统服务中的一种,WMS与AMS实际上是驻留在同一个进程中的(它们都由SystemServer启动)。
/*frameworks/base/services/java/com/android/server/SystemServer.java*/
public void run() {
….
Slog.i(TAG, "Window Manager");
wm = WindowManagerService.main(context, power, display, inputManager, uiHandler, wmHandler,
factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,!firstBoot, onlyCore);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);/*注册到Service Manager中, 其他进程可以利用Context.WINDOW_SERVICE即“window”来获取WMS服务*/
它提供了一个静态的main函数,真正的创建工作是在这里面实现的。
应用程序访问WMS的服务首先当然得通过ServiceManager
,因为WMS是实名Binder Server
;除此之外,WMS还需要针对每个Activity提供另一种匿名的实现,即IWindowSession
。
WMS和SurfaceFlinger是面向系统中所有应用程序服务的,如果客户的任何“小请求”都需要直接通过它们来处理,那么无疑会加重两者的负担,进而影响到系统的整体响应速度。所以WMS通过IWindowManager::openSession()
向外界开放一个打开Session
的接口,然后客户端的一些“琐碎”杂事就可以找Session
解决了。比如ViewRoot
在构造时就会调用这个函数:
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
public ViewRootImpl(Context context) {…
mWindowSession = WindowManagerGlobal.getWindowSession();//获取一个IWindowSession
…
}
/*frameworks/base/core/java/android/view/WindowManagerGlobal.java*/
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {…
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
imm.getClient(), imm.getInputContext());
…
在应用程序进程中,它首先通过openSession
来建立与WMS的“私有连接”。紧接着它会调用IWindowSession::relayout(IWindow window,…)
——这个函数的第一个参数就是由应用程序提供的,用于WMS回访应用进程的匿名Binder Server
。IWindow
在应用进程中的实现是W类:
static class W extends IWindow.Stub {…
W提供了包括resized
,dispatchAppVisibility
,dispatchScreenState
在内的一系列回调接口,用于WMS实时通知应用进程界面上的变化(有些变化并不是应用进程的主动意愿)。
以系统窗口StatusBar
为例,StatusBar
是SystemUI
的重要组成部分,具体就是指系统状态栏,用于显示时间、电量和信号等信息。我们来查看StatusBar
的实现类PhoneStatusBar
的addStatusBarWindow
方法,这个方法负责为StatusBar
添加Window
,如下所示。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar
private void addStatusBarWindow() {
makeStatusBarView();//1
mStatusBarWindowManager = new StatusBarWindowManager(mContext);
mRemoteInputController = new RemoteInputController(mStatusBarWindowManager,
mHeadsUpManager);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());//2
}
注释1处用于构建StatusBar
的视图。在注释2处调用了StatusBarWindowManager
的add
方法,并将StatusBar
的视图(StatusBarWindowView
)和StatusBar
的传进去。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
public void add(View statusBarView, int barHeight) {
mLp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
barHeight,
WindowManager.LayoutParams.TYPE_STATUS_BAR,//1
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.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);//2
mLpChanged = new WindowManager.LayoutParams();
mLpChanged.copyFrom(mLp);
}
首先通过创建LayoutParams
来配置StatusBar
视图的属性,包括Width、Height、Type、 Flag、Gravity、SoftInputMode
等, 关键在注释1处,设置了TYPE_STATUS_BAR
,表示StatusBar
视图的窗口类型是状态栏。在注释2处调用了WindowManager
的addView
方法,addView
方法定义在WindowManager
的父类接口ViewManager
中,而实现addView
方法的则是WindowManagerImpl
中,如下所示。
frameworks/base/core/java/android/WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
在WindowManagerImpl
的addView
方法中,接着会调用WindowManagerGlobal
的addView
方法:
rameworks/base/core/java/android/view/WindowManagerGlobal.java
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);//1
} else {
...
}
ViewRootImpl root;
View panelParentView = null;
...
root = new ViewRootImpl(view.getContext(), display);//2
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);//3
mParams.add(wparams);
}
try {
root.setView(view, wparams, panelParentView);//4
} catch (RuntimeException e) {
...
}
}
首先会对参数view
、params
和display
进行检查。注释1处,如果当前窗口要作为子窗口,就会根据父窗口对子窗口的WindowManager.LayoutParams
类型的wparams
对象进行相应调整。注释2处创建了ViewRootImp
并赋值给root
,紧接着在注释3处将root存入到ArrayList
类型的mRoots
中,除了mRoots
,mViews
和mParams
也是ArrayList
类型的,分别用于存储窗口的view
对象和WindowManager.LayoutParams
类型的wparams
对象。注释4处调用了ViewRootImpl
的setView
方法。
ViewRootImpl身负了很多职责:
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;//一个ViewRootImpl对应一棵ViewTree,因而添加到其成员变量中
…
requestLayout();//发起layout请求
…
try {…
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);/*调用Session接口*/
} catch (RemoteException e) {
…//出错处理
} finally {
//收尾工作
}
…
我们知道,WMS才是窗口管理系统。因而当应用程序新增了一个顶层View(ViewTree的根)时,是肯定要通知WMS的。在将View树(在WMS看来,它是一个“Window”,由WindowState来管理)注册到WMS前,需要注意什么呢?没错,必须先执行第一次layout,也就是调用requestLayout——WMS除了窗口管理外,还负责各种事件的派发,所以在向WMS“注册”前应用程序要确保这棵View树已经做好了接收事件的准备。
ViewRoot起到了中介的作用。作为整棵View树的“管理者”,它同时也担负着与WMS进行IPC通信的重任。具体而言,就是通过IWindowSession建立起双方的桥梁。
setView
方法中有很多逻辑,这里只截取了一小部分,主要就是调用了mWindowSession
的addToDisplay
方法。mWindowSession
是IWindowSession
类型的,它是一个Binder
对象,用于进行进程间通信,IWindowSession
是Client端的代理,它的Server端的实现为Session
,此前包含ViewRootImpl
在内的代码逻辑都是运行在本地进程的,而Session
的addToDisplay
方法则运行在WMS所在的进程。
在IWindowSession服务端的实现中(Session.java),函数addToDisplay(又直接调用了WMS中的addWindow:
frameworks/base/services/core/java/com/android/server/wm/Session.java
@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);
}
仔细分析一下ViewRoot提供给IWindowSession的参数:第二个参数是IWindow,即WMS回调ViewRoot时要用到的。第三个参数是一个int数值,是用于WMS与ViewRoot间状态同步的。在WMS服务端,这个状态同步值(sequence)记录在WindowState::mSeq中。当updateStatusBarVisibilityLocked时,sequence值有可能会改变,并通过IWindow:: dispatchSystemUiVisibilityChanged这个callback函数通知ViewRoot进行更新。第四个参数是关于属性的。第五个参数是View的可见性等。
其中并没有View对象或者“View树”相关的变量。为什么呢?
因为WMS并不关心View树所表达的具体UI内容,它只要知道各应用进程显示界面的大小、“层级值”(这些信息已经包含在WindowManager.LayoutParams中)即可。
addToDisplay方法中会调用了WMS的addWindow方法,并将自身也就是Session,作为参数传了进去,每个应用程序进程都会对应一个Session,WMS会用ArrayList来保存这些Session。这样剩下的工作就交给WMS来处理,在WMS中会为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS会将它所管理的Surface交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。
当AMS发现一个Activity即将启动时,它需要将相关信息“告知”WMS:
/*frameworks/base/services/java/com/android/server/am/ActivityStack.java*/
private final void startActivityLocked(…)
{…
mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, r.info.
screenOrientation,r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0);
WMS会把ActivityRecord记录到WMS中的HashMap
Activity从启动到最终在屏幕上显示出来,分别要经历onCreate->onStart->onResume三个状态迁移。其中onResume是当界面即将可见时才会调用的,紧接着ActivityThread就会通过WindowManagerImpl来把应用程序窗口添加到系统中。具体代码如下:
/*frameworks/base/core/java/android/app/ActivityThread.java*/
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {…
ActivityClientRecord r = performResumeActivity(token, clearHide);/*这个函数将导致
Activity的onResume被最终调用*/
…
View decor = r.window.getDecorView();/*DecorView是Activity整棵View树的最外围,
可以参见下一章的分析*/
decor.setVisibility(View.INVISIBLE);//可见性
ViewManager wm = a.getWindowManager();//得到的实际上是WindowManagerImpl对象
WindowManager.LayoutParams l = r.window.getAttributes();//获取属性
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;//窗口类型
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);//将窗口添加到WMS中
}
…
值得一提的是,函数performResumeActivity将最终调用Activity的onResume,不过中间还需要经过很多处理过程。如果这个Activity是可见的,那么就需要把它的UI窗口添加到WMS中。
一个Activity对应的View树最外围是DecorView,可以由Window.getDecorView()获取。换句话说,开发人员通常在onCreate()中利用setContentView()所设置的“Content”其实只是DecorView中的“内容”部分,而不包括标题栏等“Decor”。
对于一般的应用程序,其窗口类型(Window Type)是TYPE_BASE_APPLICATION,值为1。这也是“层级值”最低的一种窗口类型。WindowManagerImpl对象(即变量wm)则是通过Activity. getWindowManager取得的。
最后我们通过WindowManagerImpl.addView来把DecorView添加到系统中,这将导致WMS中的addWindow被调用——中间流程和我们在系统窗口添加小节的分析是完全一样的。