忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
– 服装学院的IT男
本篇已收录于Activity短暂的一生系列
欢迎一起学习讨论Android应用开发或者WMS
V:WJB6995
Q:707409815
正文
窗口显示流程一共分为以下5篇:
窗口显示-流程概览与应用端流程分析
窗口显示-第一步:addWindow
窗口显示-第二步:relayoutWindow -1
窗口显示-第二步:relayoutWindow -2
窗口显示-第三步:finishDrawingWindow
1. 流程概述
本篇开始真正看 addWindow 流程,首先从结果上对比下应用启动后窗口的区别来确认本篇的目的:
红色部分就是启动应用后多出来的部分,在 DefaultTaskDisplayArea 节点下多出来这么一个层级:
TaskActivityRecordWindowState
其中 Task 和 ActivityRecord 是如何挂载上去的在【Activity启动流程】已经介绍了,当前要分析的 addWindow 流程最重要的目标就是分析窗口对应的 WindowState 是如何创建并且挂载到窗口树中的。
也就是这一变化:
这个流程逻辑相对简单,整个流程框图如下:
-
- 应用端 Activity 执行到 onResume 说明 Activity 已经可见,下面就需要处理可见的内容
-
- 应用端 Session 调用到 WindowManagerService::addWindow 方法
-
- WMS 处理 addWindow 流程也就做了2件事:
- 创建出对应的 WindowState
- 挂载到层级树中(挂载到对应的 WindowToken 下)
2. 流程分析
上一篇知道流程已经执行到 ViewRootImpl::setView 来触发 addWindow 流程,回忆一下应用端的调用:
# ViewRootImplfinal W mWindow;public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {......res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId,mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,mTempControls, attachedFrame, compatScale);......}
这里有几个参数比较重要:
mWindow : 用于 WMS 与应用端通信
mWindowAttributes : DecorView 的参数
getHostVisibility() :可见性
inputChannel:Input 通路
看到你这些参数有个疑问:
明明是 addWindoW 流程,但是到了 WindowManagerImpl 就变成了 addView 传递的也是 DecoreView ,再到和 WMS 通信的时候,参数里连 DecoreView 都不剩了,这怎么能叫 addWindow 流程呢?
本篇将介绍WindowManagerService是如何处理剩下逻辑的文末也会回答这个问题。
2.1 WindowManagerService::addWindow方法概览
接上篇知道 Session::addToDisplayAsUser 方法调用的是 WindowManagerService::addWindow ,先看一下这个方法。
# WindowManagerService// 保存应用端 ViewRootImpl 和 WindowState 的映射关系/** Mapping from an IWindow IBinder to the server's Window object. */final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls) {......// 1.1 权限检查int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,appOp);if (res != ADD_OKAY) {return res;}// 父窗口,应用 Activity 窗口逻辑是没有父窗口的WindowState parentWindow = null;......// 拿到当前窗口类型final int type = attrs.type;......synchronized (mGlobalLock) {......// 1.2 如果窗口已经添加,直接returnif (mWindowMap.containsKey(client.asBinder())) {// 日志ProtoLog.w(WM_ERROR, "Window %s is already added", client);return WindowManagerGlobal.ADD_DUPLICATE_ADD;}......ActivityRecord activity = null;// 是否为 hasParentfinal boolean hasParent = parentWindow != null;// 2.1 拿到tokenWindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);// Activity 没有父窗口,这里也为nullfinal int rootType = hasParent ? parentWindow.mAttrs.type : type;......if (token == null) {......if (hasParent) {// Use existing parent window token for child windows.// 2.2子窗口用父窗口的 tokentoken = parentWindow.mToken;} else if (...) {......} else {// 2.3 系统窗口会创建 tokenfinal IBinder binder = attrs.token != null ? attrs.token : client.asBinder();token = new WindowToken.Builder(this, binder, type).setDisplayContent(displayContent).setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow).setRoundedCornerOverlay(isRoundedCornerOverlay).build();}} else if (rootType >= FIRST_APPLICATION_WINDOW&& rootType <= LAST_APPLICATION_WINDOW) {......} else if......// 忽略其他各种创建对 token的处理// 3.1 创建 WindowStatefinal WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);......final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();// 调整window的参数displayPolicy.adjustWindowParamsLw(win, win.mAttrs);......// 1.3 验证Window是否可以添加,主要是验证权限res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);if (res != ADD_OKAY) {// 如果不满足则直接returnreturn res;} final boolean openInputChannels = (outInputChannel != null&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);if (openInputChannels) {// 4.1 Input 事件输入通道win.openInputChannel(outInputChannel);}......// 3.2 创建SurfaceSessionwin.attach();// 3.3 窗口存入mWindowMapmWindowMap.put(client.asBinder(), win);......// 3.4 窗口挂载win.mToken.addWindow(win);displayPolicy.addWindowLw(win, attrs);......// 4.2 处理窗口焦点切换boolean focusChanged = false;if (win.canReceiveKeys()) {focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,false /*updateInputWindows*/);if (focusChanged) {imMayMove = false;}}......// 调整父容器下的元素层级win.getParent().assignChildLayers();// 4.3 更新inut焦点if (focusChanged) {displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,false /*updateInputWindows*/);}// 4.4 更新input窗口displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);// 窗口添加logProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"+ ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));......}Binder.restoreCallingIdentity(origId);return res;}
这个方法就是 addWindow 流程的核心方法了,代码很多,保留了下面4个主要逻辑:
-
- 校验处理
- 1.1 1.3 为操作权限校验
- 1.2 为限制应用端的一个 RootView 只能执行一次 addWindow
-
- Token 处理
- 这个 token 其实就是 WindowToken(ActivityRecord 是其子类)
- 获取 token,如果是子窗口就从父窗口拿,没有就从参数里拿
- 如果是系统窗口就会在2.3出根据窗口类型创建出一个 WindowToken
-
- WindowState 处理
- 3.1 创建 WindowState
- 2.2 执行 WindowState::attach 会创建 SurfaceSession
- 3.3 将新建的 WindowState 和 W 映射,存入 mWindowMap
- 3.4 窗口挂载
-
- Input 和焦点处理
当然这个方法实际上做的事肯定不止这些,只是根据我的个人理解整理出了几个比较重要的处理点。
当前分析的 addWindow 主流程,所以分析2,3两点,也就是 Token 和 WindowState 的处理逻辑。
3 Token相关
首先给一个结论,当前分析的场景,这个 Token 就是 Activity 启动流程中创建 ActivityRecord 时创建的 Token ,而 ActivityRecord 是 WindowToken 的子类。
在 【WindowContainer窗口树】介绍过,WindowState 的父节点大部分情况是 WindowToken ,而且在上一篇看到 dump 启动应前后的窗口树区别,明确知道 WindowState 是挂载到 ActivityRecord 下的,
在分析 WindowState 的创建和挂载前,需要先给它找到它的父节点: WindowToken 。这也是 WindowManagerService::addWindow 方法中比较靠前执行的逻辑。
根据上一小节的分析,当前场景的 Token 来自参数“attrs.token” 。这个参数是应用端传递过来的,上一篇在分析 WindowManagerGlobal::addView 方法的时候提到在Window::adjustLayoutParamsForSubWindow 方法对赋值 token 给参数,现在看一下这个方法。
# Window// 应用Tokenprivate IBinder mAppToken;void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {......if (wp.token == null) {wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; // activity的window在这里设置token}......}
mContainer 唯一赋值的地方在 Window::setContainer 方法,当前没调用,所以 wp.token 最终的值是为 mAppToken ,而mAppToken 的赋值在给 Window 设置 WindowManager 的时候赋值,也就是setWindowManager 方法,这里的 token 就是 ActivityRecord 的 token 。
下面这张图可以更直观的看到 Token 的传递:
-
- WindowToken 内部有个成本变量 token ,ActivityRecord 是其子类
-
- Activity 启动过程中会先创建 ActivityRecord ,在创建 ActivityRecord 的时候会创建一个匿名 Token 对象,并保存在变量 token 中
-
- 随着启动流程的执行,会在 ActivityTaskSupervisor::realStartActivityLocked 方法里构建事务,这个时候 token 就被保存在 ClientTransaction 的成员变量 mActivityToken
-
- ClientTransaction 提供了一个 getActivityToken 方法返回 mActivityToken 。这个方法在具体的事务执行时,比如 LaunchActivityItem::execute 方法执行,会作为参数传递过去
-
- LaunchActivityItem::execute 方法会构建一个 ActivityClientRecord ,构建方法需要 Token 参数,这个时候 Token 就被保存在 ActivityClientRecord 的成员变量 token 中
-
- 接下里就到了应用进程,应用进程执行 ActivityThread::performLaunchActivity 方法开始处理 Activity 启动流程,ActivityClientRecord 作为参数被传递了过来
-
- ActivityThread::performLaunchActivity 方法内部会执行 Activity::attach 方法,这个方法需要一个 Token 作为参数,传递的就是从 ActivityClientRecord 里取出的 token
-
- Activity::attach 方放会将 Token 赋值给成员变量 mToken
-
- Window 创建后会执行 Window::setWindowManager ,这个时候会将 mToken 作为参数传递进去,保存在 Window 的成员变量 mAppToken 中
-
- 在执行 WindowManagerGlobal::addView 时会执行 Window::adjustLayoutParamsForSubWindow 调整参数,这个时候 Token 就被复制到 WindowManager.LayoutParams 下的 token 变量中
-
- 执行 addWindow 流程时,WindowManager.LayoutParams 会被传递到 WMS ,这样 Token 也就被传递了过去
3.1 补充 生命周期事务流程图
这里补充下 system_service 是如何通过 ClientTransaction 来完成应用端生命周期相关执行的流程,具体代码不是当前重点,就不具体分析了。
4. WindowState的创建与挂载
addWindow 流程中 WindowState 的创建与挂载是重点,回顾一下这一流程层级树的变化:
4.1 WindowState的创建
在 WindowManagerService::addWindow 方法中,执行了 WindowState 的创建,代码如下:
# WindowManagerServicepublic int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls) {......// WindowToken相关处理// 创建WindowStatefinal WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);......}
这里注意几个参数,然后直接看WindowState的构造方法
# WindowStatefinal IWindow mClient;@NonNull WindowToken mToken;// The same object as mToken if this is an app window and null for non-app windows.// 与 mToken 相同的对象(如果这是应用程序窗口),而对于非应用程序窗口为null// 说人话就是应用窗口才有ActivityRecordActivityRecord mActivityRecord;// 层级final int mBaseLayer;WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,PowerManagerWrapper powerManagerWrapper) {......mClient = c;......// 保存tokenmToken = token;// 只有 ActivityRecord 重写了 asActivityRecord 其他默认返回努力了mActivityRecord = mToken.asActivityRecord();......//子窗口处理if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW){......}else {// Activity的窗口指为 2 * 10000 + 1000 = 21000mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;......}}
创建 WindowState 有2个重要的参数 :client,和 token 。这个 client 代表着客户端也就是 ViewRootImpl 的内部类 W ,另一个参数就是上节的 Token 。
WindowState 以后会经常看到,不过当前只要知道在 WindowManagerService::addWindow 会创建出一个 WindowState 对象即可。
4.2 WindowState的挂载
WindowState 创建好后自然是需要挂载到窗口树的,操作也很简单,直接添加到对应的 (ActivityRecord)WindowToken 下就好。
# WindowManagerService// ViewRootImpl和WindowState的mapfinal HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls) {......// 窗口已经添加,直接returnif (mWindowMap.containsKey(client.asBinder())) {// 打印logProtoLog.w(WM_ERROR, "Window %s is already added", client);return WindowManagerGlobal.ADD_DUPLICATE_ADD;}......// WindowToken相关处理......// WindowState的创建// WindowState的挂载win.attach();// 1. 存进mapmWindowMap.put(client.asBinder(), win);......// 2. 挂载win.mToken.addWindow(win);......}
-
- 在看挂载前先看一下 mWindowMap 这个数据结构,key 是一个 IBinder,value 是 WindowState ,这边将新创建的 WindowState 作为 value 添加到了 map 中,前面说过 client是应用端 ViewRootImpl 下的 “W”这个类,也就是说在 WMS 中应用端的这个 ViewRootImpl 和为其创建的 WindowState 已经被记录在 mWindowMap 中了。
在执行WMS::addWindow方法开始的时候就会尝试通过 clent 从 mWindowMap 获取值,如果获取到了说明已经执行过 addWindow 则进行 return 不执行后面逻辑。
-
- 这里是窗口的挂载,“win.mToken” 这里的 mToken 刚刚看到是创建 WindowState 的时候传递的 token 也就是 ActivityRecord (WindowToken)。
也就是说调用的是 ActivityRecord::addWindow 方法进行挂载的。
# ActivityRecord@Overridevoid addWindow(WindowState w) {super.addWindow(w);checkKeyguardFlagsChanged();}
直接调用其父类方法,ActivityRecord 父类是 WindowToken
# WindowTokenvoid addWindow(final WindowState win) {// WindowState 挂载日志ProtoLog.d(WM_DEBUG_FOCUS,"addWindow: win=%s Callers=%s", win, Debug.getCallers(5));if (win.isChildWindow()) {// Child windows are added to their parent windows.// 子窗口的父类应该是WindowState所以不执行后续return;}// This token is created from WindowContext and the client requests to addView now, create a// surface for this token.// 真正添加进子容器if (!mChildren.contains(win)) {// 日志ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", win, this);// 挂载(添加进孩子容器),有一个比较方法addChild(win, mWindowComparator);// 记录有窗口边框mWmService.mWindowsChanged = true;// TODO: Should we also be setting layout needed here and other places?}}
执行完 WindowContainer::addChild 方法后 WindowState 已经被添加到层级树中了,挂在到对应的 ActivityRecord 下。
当然这里需要注意 WindowToken::addWindow 最终也是调用父类 WindowContainer::addChild 将 WindowState 添加到自己的孩子中,这里传递了一个mWindowComparator。
4.3 挂载的位置
WindowContainer::addChild 方法被定义在基类,也就是容器添加孩子时都会按一定规则添加,当然默认其实还是按顺序,但是有的时候也有特殊情况,所以这个方法提供了一个参数,使得可以在具体场景控制具体的添加逻辑。
# WindowContainerprotected void addChild(E child, Comparator<E> comparator) {// 检查子元素是否已经被其他容器拥有,如果是,则抛出异常if (!child.mReparenting && child.getParent() != null) {throw new IllegalArgumentException("addChild: container=" + child.getName()+ " is already a child of container=" + child.getParent().getName()+ " can't add to container=" + getName());}// 初始化插入位置为-1,表示尚未找到合适的插入位置int positionToAdd = -1;// 如果有比较器则进行比较// 遍历当前容器中的所有子元素if (comparator != null) {final int count = mChildren.size();// 使用比较器比较待插入的子元素和当前容器中的子元素for (int i = 0; i < count; i++) {// 如果比较结果小于0,表示待插入元素应该位于当前元素之前if (comparator.compare(child, mChildren.get(i)) < 0) {positionToAdd = i;break;}}}// 没有比较器或者比较的结果还是-1 ,则添加到最后(大部分场景)if (positionToAdd == -1) {mChildren.add(child);} else {// 如果比较器计算出了准确位置,则按要求添加mChildren.add(positionToAdd, child);}// Set the parent after we've actually added a child in case a subclass depends on this.// 调用孩子容器设置当前容器为其父节点child.setParent(this);}
-
- 方法目的就是添加子元素到父容器中,但是可以根据 comparator 比较规则添加到正确的位置
-
- 比较方式很简单,拿当前需要添加的元素和容器内其他元素逐个比较,如果比较 comparator 返回值小于0,则添加到“被比较”的元素前面
-
- 有2种情况,是按顺序添加到容器末尾
- 3.1 没有比较器。positionToAdd 为默认值 -1
- 3.2 和每个元素比较的返回值都大于0,说明要添加其后面,这个时候 positionToAdd 还是为默认值 -1
-
- setParent 调用孩子容器设置当前容器为其父节点,另外还会将 mSyncState 变量设置为 SYNC_STATE_WAITING_FOR_DRAW
当前场景,父容器 ActivityRecord 还是是空的,所以没什么意义。
不过既然看到这里,就继续分析,根据分析,当前逻辑调用的比较器是 WindowToken下的 mWindowComparator 。
4.3.1 addWindow是顺序-- WindowToken下的 mWindowComparator
# WindowTokenprivate final Comparator<WindowState> mWindowComparator =(WindowState newWindow, WindowState existingWindow) -> {......return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;};protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,WindowState existingWindow) {// 就是比较两个窗口的mBaseLayerreturn newWindow.mBaseLayer >= existingWindow.mBaseLayer;}
这里的 newWindow 和 existingWindow 当然是一个当前需要添加进容器的 WindowState 和上一个存在的 WindowState
-
- 对 WindowToken 的子窗口进行比较排序,1和-1表示相对顺序
-
- 返回 true,则是 1,表示插入在后面。 也就是 newWindow 的 mBaseLayer 大于原来的
-
- 返回 false,则是 -1,表示插入在前面,也就是 newWindow 的 mBaseLayer 小于原来的
简单来说就是比较 mBaseLayer 来判断当前新添加的是放在哪个位置。 正常情况都是按顺添加,也就是后添加的在最上面。
这个 mBaseLayer 在 创建 WindowState 赋值,逻辑也比较简单,应用窗口的值计算后就是 21000 ,假设有2个应用窗口,那值都是一样的,就按序添加了。
这个值自己可以根据窗口类型计算,也可以使用命名 “adb shell dumpsys window windows” dump,然后搜 “mBaseLayer=” 确认。
addWindow 流程到这也就结束了,在三个流程里算比较简单的了,就做了2件事:
-
- 创建 WindowState
-
- 挂载到窗口树上