浅析 android 应用界面的展现流程(三)会话的创建
在《浅析 android 应用界面的展现流程(二)布局与视图的创建》最后我们提到,ViewRootImpl.setView() 方法第一步是将客户端与服务端联系起来,这一步不仅仅包括了将客户端的 token 传到 WMS 中,也包括了在 WMS 中创建对应 WindowState、SurfaceSession 等对象,本文先从一些重要的对象的初始化开始,到 WindowState 对象的建立,逐步把应用程序与 WMS 之间的联系说清楚。
4. 与 WindowManagerService 建立连接
回顾一下 ViewRootImpl.setView() 的源码:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
......
try {
......
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel); // 与 WMS 通信添加该窗体对应的 WindowState
} catch (RemoteException e) {
......
}
......
}
}
}
4.1 Session 与 IWindowSession
以前做过 web 端的看官肯定对 Session 不会陌生,它代表一次登录的会话,在一次会话中我们拥有一个 session id,会话期间我们不需要再次登录,服务端通过客户端传过来的 session id 就可以找到对应会话场景,会话可能会有超时时间,超时后 session id 失效,就需要再次登录。
我们先来看下这个 mWindowSession 是什么,在 ViewRootImpl 的构造函数中:
public ViewRootImpl(Context context, Display display) {
......
mWindowSession = WindowManagerGlobal.getWindowSession();
......
}
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
imm.getClient(), imm.getInputContext());
......
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
可以看出这里会通过 Binder 调用 openSession() 在 WMS 端打开一个Session,并将其返回,服务端实现在 WindowManagerService.java 中:
public IWindowSession openSession(IInputMethodClient client,
IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
Session session = new Session(this, client, inputContext);
return session;
}
其中 client 其实就是应用程序传过来的 IInputMethodClient Binder 对象,与 Session 绑定。这样一来,在客户端就可以用这个 IWindowSession 对象与 WMS 通信,比如下面要说的通过 addToDisplay 接口创建对应的 WindowState 对象,还有在下一篇文章中要说的 relayout()。(顺便吐个槽,在这里创建 session 没有鉴权,比如调用者进程是否是在 ActivityManagerService.mPidsSelfLocked 中之类的)。
这个 Session 与应用程序进程对应,在客户端,IWindowSession 也是单例。
4.2 addToDisplay
addToDisplay 会将 ViewRootImpl 中的 W 对象(IWindow.Stub)传到 WMS 端,这个对象是在 ViewRootImpl 构造函数中初始化,不仅仅提供了 WMS 与 Activity 通信的通道,而且在 WMS 中作为一个 token,是 WindowState 所在映射的 key。
下面来看 addToDisplay 的服务端实现:
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets,
InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}
调用的实际是 ActivityManagerService.addWindow(),这个方法较为复杂,我们分开来说:
4.2.1 权限控制
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
int[] appOp = new int[1];
1. int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
......
}
mPolicy 其实就是 PhoneWindowManager,它是在 WindowManagerService 初始化时创建的,来看它的 checkAddPermission 方法:
public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
int type = attrs.type;
outAppOp[0] = AppOpsManager.OP_NONE;
if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
|| type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
return WindowManagerGlobal.ADD_OKAY;
}
String permission = null;
switch (type) {
case TYPE_TOAST:
break;
case TYPE_DREAM:
case TYPE_INPUT_METHOD:
case TYPE_WALLPAPER:
case TYPE_PRIVATE_PRESENTATION:
break;
case TYPE_PHONE:
case TYPE_PRIORITY_PHONE:
case TYPE_SYSTEM_ALERT:
case TYPE_SYSTEM_ERROR:
case TYPE_SYSTEM_OVERLAY:
permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
break;
default:
permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
}
if (permission != null) {
if (mContext.checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
}
}
return WindowManagerGlobal.ADD_OKAY;
}
类型从 WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW 到 WindowManager.LayoutParams.LAST_SYSTEM_WINDOW 的窗口都是系统级的,如果类型不是系统级则直接返回 ADD_OKAY 表示允许,如果是系统级,则需要 SYSTEM_ALERT_WINDOW 或者 INTERNAL_SYSTEM_WINDOW 权限,然后靠 checkCallingOrSelfPermission 检验调用者权限。可以看出,当我们做一些悬浮窗一类的应用时,一般会把悬浮窗的类型设置为 TYPE_PHONE、TYPE_SYSTEM_ALERT 甚至 TYPE_SYSTEM_ERROR,需要 SYSTEM_ALERT_WINDOW 权限,就是在这里进行权限验证的了。
4.2.2 token
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
......
boolean addToken = false;
2. WindowToken token = mTokenMap.get(attrs.token);
......
}
mTokenMap 是以 WindowManager.LayoutParams.token 为 key,WindowToken 为 value 的映射。
attrs.token
WindowManager.LayoutParams.token 是在 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);
}
......
}
作为 Activity 绑定的 WindowManagerImpl,parentWindow 就是 Activity 所在的 PhoneWindow(具体流程可以回顾下《浅析 android 应用界面的展现流程(二)UI与布局的准备》),转到 Window.adjustLayoutParamsForSubWindow(Window.java):
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
CharSequence curTitle = wp.getTitle();
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
......
} else {
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
......
}
......
}
在这里 mContainer = null,返回的是 Window.mAppToken 了,这个 IBinder 对象其实就是在 Activity.attach 过程中塞进来,其实是 ActivityClientRecord.token,这其实是 AMS 传过来的 ActivityRecord.appToken(代码就不再展开了,大家可以顺着这条线捋下去)。
所以作为应用程序界面 Activity,WindowManager.LayoutParams.token 对应 ActivityRecord 中的 appToken:
final IApplicationToken.Stub appToken; // window manager token
ActivityRecord 是每个 Activity 启动时新建的代表一个运行的 Activity 信息的对象,appToken 是在 ActivityRecord 构造函数中初始化的,即:
appToken = new Token(this);
它其实是个 IApplicationToken 的服务端实现了,虽然这个名字看起来很像是一个 app 只有一个单例,但是它确实是与 ActivityRecord 对应的。
mTokenMap
mTokenMap 是在 startActivitiy 时添加对应的 WindowToken 的,本文不是讲述 Activity 启动流程的,在这里只来看 WindowToken 的初始化:
<center>
图 4.1 从 startActivitiy 到 addAppToken(Android 4.4)</center>
我们只来看第7步:addAppToken(WindowManagerService.java):
public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
int configChanges) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addAppToken()")) { // 鉴权
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
......
synchronized(mWindowMap) {
AppWindowToken atoken = findAppWindowToken(token.asBinder());
if (atoken != null) {
Slog.w(TAG, "Attempted to add existing app token: " + token);
return;
}
atoken = new AppWindowToken(this, token);
atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
atoken.groupId = taskId;
atoken.appFullscreen = fullscreen;
atoken.showWhenLocked = showWhenLocked;
atoken.requestedOrientation = requestedOrientation;
atoken.layoutConfigChanges = (configChanges &
(ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
......
mTokenMap.put(token.asBinder(), atoken);
......
}
}
可以发现,这个函数就是在鉴权之后创建一个 AppWindowToken 以此为 value,传进来的 IApplicationToken 对象为 key,也就是前面提到的 ActivityRecord 中的 appToken。
总之就是一句话:
在 WindowManagerService.addWindow 中传入 WindowManager.LayoutParams attrs,其中 attrs.token 就是在 startActivitiy 时向 mTokenMap 添加的 AppWindowToken 所对应的 key。所以作为 Activity 启动,在 addWindow 中:
WindowToken token = mTokenMap.get(attrs.token);
取得的 token 一定不为空。
4.2.3 创建 WindowState
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
......
3. win = new WindowState(this, session, client, token,
attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
......
}
WindowState 的构造函数,构造函数中关键字段的初始化都已经写到注释里了:
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
mService = service; // wms
mSession = s; // IWindowSession - Session,代表一个应用程序
mClient = c; // 应用程序进程的一个类型为 W 的 Binder 本地对象,通过它可以与运行在应用程序进程这一侧的 Activity 组件进行通信
mAppOp = appOp;
mToken = token; // mTokenMap 中取得的,一个 Activity 对应一个,可以唯一标识一个窗口
mOwnerUid = s.mUid; // 应用程序的 UID
mWindowId = new IWindowId.Stub() { // 提供给外界的一些回调
@Override
public void registerFocusObserver(IWindowFocusObserver observer) {
WindowState.this.registerFocusObserver(observer);
}
@Override
public void unregisterFocusObserver(IWindowFocusObserver observer) {
WindowState.this.unregisterFocusObserver(observer);
}
@Override
public boolean isFocused() {
return WindowState.this.isFocused();
}
};
mAttrs.copyFrom(a); // addView 传进来的 WindowManager.LayoutParams 对象的拷贝
mViewVisibility = viewVisibility; // 当前所创建的WindowState对象所描述的窗口视图的可见性
mDisplayContent = displayContent; //
mPolicy = mService.mPolicy;
mContext = mService.mContext; // WMS 的 Context,当然 SystemServer 里就一个
DeathRecipient deathRecipient = new DeathRecipient();
mSeq = seq;
mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Window " + this + " client=" + c.asBinder()
+ " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
try {
c.asBinder().linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
mDeathRecipient = null;
mAttachedWindow = null;
mLayoutAttached = false;
mIsImWindow = false;
mIsWallpaper = false;
mIsFloatingLayer = false;
mBaseLayer = 0;
mSubLayer = 0;
mInputWindowHandle = null;
mWinAnimator = null;
return;
}
mDeathRecipient = deathRecipient;
if ((mAttrs.type >= FIRST_SUB_WINDOW &&
mAttrs.type <= LAST_SUB_WINDOW)) {
......
} else {
// The multiplier here is to reserve space for multiple
// windows in the same type layer.
mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)
* WindowManagerService.TYPE_LAYER_MULTIPLIER
+ WindowManagerService.TYPE_LAYER_OFFSET; // 窗口的基础 Z 轴位置
mSubLayer = 0; // 如果不是子窗体,一直为0
mAttachedWindow = null; // 如果不是子窗体,一直为 null
mLayoutAttached = false; // 如果不是子窗体,一直为false
mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
|| mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
mIsFloatingLayer = mIsImWindow || mIsWallpaper;
}
WindowState appWin = this;
while (appWin.mAttachedWindow != null) {
appWin = appWin.mAttachedWindow;
}
WindowToken appToken = appWin.mToken;
while (appToken.appWindowToken == null) {
WindowToken parent = mService.mTokenMap.get(appToken.token);
if (parent == null || appToken == parent) {
break;
}
appToken = parent;
}
mRootToken = appToken;
mAppToken = appToken.appWindowToken;
mWinAnimator = new WindowStateAnimator(this); // 一个 WindowState 对应一个 WindowStateAnimator,动画相关的控制
mWinAnimator.mAlpha = a.alpha; // 该窗体的 alpha 通道
mRequestedWidth = 0;
mRequestedHeight = 0;
mLastRequestedWidth = 0;
mLastRequestedHeight = 0;
mXOffset = 0;
mYOffset = 0;
mLayer = 0;
mInputWindowHandle = new InputWindowHandle(
mAppToken != null ? mAppToken.mInputApplicationHandle : null, this,
displayContent.getDisplayId());
}
4.2.4 WindowState 的调整
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
......
4. mPolicy.adjustWindowParamsLw(win.mAttrs);
5. win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
6. res = mPolicy.prepareAddWindowLw(win, attrs);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
if (outInputChannel != null && (attrs.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
String name = win.makeInputChannelName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
win.setInputChannel(inputChannels[0]);
inputChannels[1].transferTo(outInputChannel);
7. mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
}
......
}
步骤4:实际调用的是 PhoneWindowManager 的 adjustWindowParamsLw() 方法:
public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
switch (attrs.type) {
case TYPE_SYSTEM_OVERLAY:
case TYPE_SECURE_SYSTEM_OVERLAY:
// These types of windows can't receive input events.
attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
break;
}
}
可以看出就是根据窗口类型调整属性,凡是 TYPE_SYSTEM_OVERLAY 和 TYPE_SECURE_SYSTEM_OVERLAY 类型的均无焦点。
步骤5:设置这个窗口是否是只对自己UID可见。
步骤6:根据窗口的类型鉴权。
步骤7:创建IO输入事件连接通道的,以便正在增加的窗口可以接收到系统所发生的键盘和触摸屏事件,可以参考罗老师的这篇文章。
这三步基本跟我们的主线 - 创建session没有太大的关系,逻辑也比较简单,各位看官可以自行分析下。
4.2.5 attach
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
......
8. win.attach(); // SurfaceSession
mWindowMap.put(client.asBinder(), win); // 把 IWindow 和 WindowState 关联起来
......
}
下面通过 WindowState.attach() 方法创建一个关联的SurfaceSession对象,以便可以用来和SurfaceFlinger服务通信(WindowState.java):
void attach() {
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Attaching " + this + " token=" + mToken
+ ", list=" + mToken.windows);
mSession.windowAddedLocked();
}
展开 Session.windowAddedLocked()(Session.java):
void windowAddedLocked() {
if (mSurfaceSession == null) {
if (WindowManagerService.localLOGV) Slog.v(
WindowManagerService.TAG, "First window added to " + this + ", creating SurfaceSession");
mSurfaceSession = new SurfaceSession();
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
WindowManagerService.TAG, " NEW SURFACE SESSION " + mSurfaceSession);
mService.mSessions.add(this);
}
mNumWindow++;
}
我们发现在 WindowState 创建成功后并执行 windowAddedLocked 创建 SurfaceSession 后才会把 Session 加到 WindowManagerService.mSessions 中,展开 SurfaceSession 的构造函数:
public SurfaceSession() {
mNativeClient = nativeCreate();
}
nativeCreate() 的实现在 /framework/base/core/jni/android_view_SurfaceSession.cpp:
static jint nativeCreate(JNIEnv* env, jclass clazz) {
SurfaceComposerClient* client = new SurfaceComposerClient();
client->incStrong((void*)nativeCreate);
return reinterpret_cast<jint>(client);
}
可以发现,SurfaceSession 实际对应到C++ 层中的一个 SurfaceComposerClient 对象,nativeCreate() 返回的就是 SurfaceComposerClient 对象的指针。SurfaceComposerClient 对象主要是用来在应用程序进程和 SurfaceFlinger 服务之间建立连接的,以便应用程序进程可以为运行在它里面的应用程序窗口请求SurfaceComposerClient创建绘制表面(Surface)的操作等。
4.3 小结
至此,与 WMS 的连接已经过完了,总结一下,就是:Session/IWindowSession - SurfaceSession - SurfaceComposerClient(进程内单例),W/IWindow - IApplicationToken - AppWindowToken/WindowToken(一个窗体一个) 的对应关系。
下一篇文章我们将会继续展开《浅析 android 应用界面的展现流程(二)UI与布局的准备》中提到的 ViewRootImpl.setView 的第二步:requestLayout()。
首先表示感谢,我也在看andorid显示部分的逻辑,跟着这里的流程走轻松很多,提个问题:
[[[
4. 与 WindowManagerService 建立连接
回顾一下 WindowManagerImpl.setView() 的源码:
]]]
这里似乎有问题,setView方法应该是ViewRootImpl类中的方法?也可能是我的代码是16年的代码,和当时的有出入?
感谢指出,确实是笔误了 (^_^)