一、概述
这次开始到了WindowManagerService(WMS),你可以把它看做一个WindowManager,只不过呢,属于系统服务进程(system_server)中的一员,和应用不在同一进程,所以涉及了一些跨进程通信的内容,如果不清楚的可以去补一下Binder通信机制。这些不是重点,这次重点讲的是上堂课没有讲完的addToDisplayAsUser。
二、WMS
关于WMS启动的过程,之后会和AMS(ATMS)文章中一起讲解。如果已经有了解Android特有的跨进程通信方式Binder的基础,不难知道WMS是一个Binder对象,这对之后的讲解有点帮助。
上篇Window的文章不知道大家还有没有印象mWindowSession#addToDisplayAsUser,这里的Session是通过WindowManagerGlobal#getWindowSession提供的binder对象,帮助我们完成跨进程通信。
//WindowManagerGlobal.java
@UnsupportedAppUsage
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
if (sWindowManagerService != null) {
ValueAnimator.setDurationScale(
sWindowManagerService.getCurrentAnimatorScale());
sUseBLASTAdapter = sWindowManagerService.useBLAST();
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowManagerService;
}
}
@UnsupportedAppUsage
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
// Emulate the legacy behavior. The global instance of InputMethodManager
// was instantiated here.
// TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
这段代码很长,但是很简单,目的是请求服务端的WindowSession对象,即服务端的binder对象。我们看它是怎么做的。首先先通过IPC通信方式获取WMS,之前说过这也是一个binder对象。然后借助WMS的openSession拿到服务端的WindowSession对象。这样做的好处是什么呢?将两次binder通信简化成了一次binder通信,优化了跨进程通信的效率。
服务端的WindowSession对象是一个叫做Session的类。
既然已经进入Session这个类,我们直接来看一下addToDisplayAsUser做了什么。
//Session.java
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
UserHandle.getUserId(mUid), requestedVisibleTypes, outInputChannel, outInsetsState,
outActiveControls, outAttachedFrame, outSizeCompatScale);
}
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibleTypes, outInputChannel, outInsetsState, outActiveControls,
outAttachedFrame, outSizeCompatScale);
}
它调用了内部的addToDisplay方法,一般我会把这种行为相同,但是命名有些许不同的方法叫做衍生方法。这里面的mService就是WMS,因为它们属于同一个进程,所以这里的调用已经不是跨进程通信了。并且调用了WMS#addWindow方法。
上面跟了一大段,我们发现现在才是真正进入了WMS,可见系统源码对于职责的封装不可谓是不严格啊,我们继续分析。
//WindowManagerService.java
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
...
//获取屏幕内容对象,根据屏幕id(一屏多显可能也会有多个id)
final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
...
//一个window集合,保存的key是ViewRootImpl$w的binder对象(也可以理解为是window的binder对象)
if (mWindowMap.containsKey(client.asBinder())) {
ProtoLog.w(WM_ERROR, "Window %s is already added", client);
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
...
//创建WindowState,服务端管理的window,记录了window全部信息
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
...
win.attach();
//添加到map集合中,key是客户端w的binder对象,value是WindwoState
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
...
//WindowState拿到token添加当前的WindowState进去,双向绑定
win.mToken.addWindow(win);
由于WMS#addWindow篇幅很多,省略了很多细节之后看上去就清楚多了。重要的事情注解都有说明:
- 通过DisplayContent获取token
- 根据客户端window创建WindowState对象,保存Window有关的所有信息
- 把WindowState缓存到WindowMap集合中,key是客户端的w的binder对象
- 将WindowState和token双向绑定
三、总结
1、ViewRootImpl#setView通过WindwoSession来管理window
2、借助WMS的binder对象调用openSession来获取WindowSession对象,服务端叫Session
3、WMS#addWindow根据客户端window创建了对应的WindowState对象
4、用一个WindowMap集合缓存,key是客户端ViewRootImpl$W的binder对象
5、WindowState和WindowToken双向绑定
至此关于ViewRootImpl#setView中的关于WMS#addToDisplayAsUser的逻辑我们就分析完了。这样一趟分析下来,发现其实也没有做多少事,但是有一点好奇的不知道大家有没有注意到,方法是setView,而WMS中全程操作的对象都是window,并且setView方法中的View并没有通过addToDisplayAsUser方法传递下去。那么View之后又是怎么处理的呢?我们下一篇文章中继续探索Window的真相。