【Android 13源码分析】WMS-添加窗口(addWindow)流程-2-SystemService进程处理

上一篇流程已经执行到 ViewRootImpl::setView方法了,也就意味着应用进程的逻辑到了终点,剩下的将由 SystemService进程来处理。

回顾一下应用进程的相关调用链:

arduino 复制代码
LaunchActivityItem::execute
    ActivityThread::handleLaunchActivity
        ActivityThread::performLaunchActivity
            Instrumentation::newActivity      --- 创建Activity
            Activity::attach                  --- 创建Window
                Window::init
                Window::setWindowManager
            Instrumentation::callActivityOnCreate  
                Activity::performCreate
                    Activity::onCreate 

ResumeActivityItem::execute
    ActivityThread::handleResumeActivity
        ActivityThread::performResumeActivity   
            Activity::performResume   
                Instrumentation::callActivityOnResume
                    Activity::onResume        
        WindowManagerImpl::addView           --- 创建ViewRootImpl
            WindowManagerGlobal::addView   
                ViewRootImpl::setView        --- 与WMS通信 addView

另外还留下了一个疑问:

明明是addWindo流程,但是到了WindowManagerImpl就变成了addView,传递的也是DecoreView,再到和WMS同信的时候,参数里连DecoreView都不剩了,这怎么能叫addWindow流程呢?

本篇将介绍WindowManagerService是如何处理剩下逻辑的,文末也会回答这个问题。

1. SystemServer进程处理

上篇看过这张图,在 system_service 要做的最重要的就是WindowState创建和挂载,也是本篇需要分析的流程。

1.1 WindowManagerService::addWindow方法概览

接上篇知道 Session::addToDisplayAsUser 方法最终调用的是WindowManagerService::addWindow,先看一下这个方法。

ini 复制代码
#  WindowManagerService
    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
            ......
            // 权限检查
            int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
                    appOp);
            if (res != ADD_OKAY) {
                return res;
            }
            //  父窗口,应用Activity逻辑是没有父窗口的。
            WindowState parentWindow = null;
            ......
            synchronized (mGlobalLock) {
                    ......
                    // 窗口已经添加,直接return
                    if (mWindowMap.containsKey(client.asBinder())) {
                        ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                        return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                    }
                    ......
                    
                    ActivityRecord activity = null;
                    // 1. 是否为hasParent
                    final boolean hasParent = parentWindow != null;
                    // 2. 处理token
                    WindowToken token = displayContent.getWindowToken(
                            hasParent ? parentWindow.mAttrs.token : attrs.token);
                    ......
                    if (token == null) {
                        ......
                        if (hasParent) {
                            // Use existing parent window token for child windows.
                            token = parentWindow.mToken;
                        } else if (mWindowContextListenerController.hasListener(windowContextToken)) {
                            // Respect the window context token if the user provided it.
                            final IBinder binder = attrs.token != null ? attrs.token : windowContextToken;
                            final Bundle options = mWindowContextListenerController
                                    .getOptions(windowContextToken);
                            token = new WindowToken.Builder(this, binder, type)
                                    .setDisplayContent(displayContent)
                                    .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                                    .setRoundedCornerOverlay(isRoundedCornerOverlay)
                                    .setFromClientToken(true)
                                    .setOptions(options)
                                    .build();
                        } else {
                            final 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. 创建WindowState
                    final WindowState win = new WindowState(this, session, client, token, parentWindow,
                            appOp[0], attrs, viewVisibility, session.mUid, userId,
                            session.mCanAddInternalSystemWindow);
                    ......
                    final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
                    // 4. 调整window的参数
                    displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
                    attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
                    attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), callingUid,
                            callingPid);
                    win.setRequestedVisibilities(requestedVisibilities);
                    // 5. 验证Window是否可以添加,主要是验证权限
                    res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
                    if (res != ADD_OKAY) {
                        // 如果不满足则直接return
                        return res;
                    }   
                    final boolean openInputChannels = (outInputChannel != null
                            && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
                    if  (openInputChannels) {
                        // 6.打开事件输入通道
                        win.openInputChannel(outInputChannel);
                    }
                    ......
                    // 7. 窗口添加进容器
                    win.attach();
                    mWindowMap.put(client.asBinder(), win);
                    win.initAppOpsState();
                    ......
                    win.mToken.addWindow(win);
                    displayPolicy.addWindowLw(win, attrs);
                    ......
                    // 8.1 处理窗口焦点切换
                    boolean focusChanged = false;
                    if (win.canReceiveKeys()) {
                        focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                                false /*updateInputWindows*/);
                        if (focusChanged) {
                            imMayMove = false;
                        }
                    }

                    if (imMayMove) {
                        displayContent.computeImeTarget(true /* updateImeTarget */);
                    }

                    // 9. 分配层级
                    win.getParent().assignChildLayers();
                    // 8.2 更新inut焦点
                    if (focusChanged) {
                        displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
                                false /*updateInputWindows*/);
                    }
                    // 8.3 更新input窗口
                    displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
                    // 窗口添加log
                    ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
                            + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
                    ......
                }
                Binder.restoreCallingIdentity(origId);
                return res;
        }

1.2 小结-addWindow主要处理了以下9个事件

    1. 如果是SubWindow判断当前是否有parentWindow (当前是Activity的逻辑,没有父窗口)
    1. 获取token ----DisplayContent.mTokenMap中获取
    • 2.1 获取失败则创建token
    • 2.2 获取成功将token转换为ActivitRecord
    • 2.3 对token做校验,该返回报错就返回
    1. 根据token等信息创建WindowState
    1. 调整window的参数 -------DisplayPolicy::adjustWindowParamsLw
    1. 验证Window是否可以添加 -----res = displayPolicy.validateAddingWindowLw
    1. 打开input事件输入通道-----WIndowState::openInputChannel 这样Activity能接收到input事件
    1. 添加窗口进入容器
    • 7.1 通知WindowState已经添加 -----WindowState::attach
    • 7.2 将WindowState存入WMS的mWindowMap----mWindowMap.put(client.asBinder(), win);
    • 7.3 将WindowState添加进WindowState.mToken集合,同一个类型的界面,进行排序 ---- WindowToken::addWindow
    1. 3个地方都是对焦点窗口相关的处理
    1. 分配层级---------- win.getParent().assignChildLayers();

当然这个方法实际上做的事肯定不止这些,只是根据我的个人理解整理出了比较重要的9个事情处理。

当前分析的addWindow主流程,其中和当前跟踪相关的分别为2,3,7,下面详细分析一下这3块,其中 3和7 分别对应 WindowState的创建与挂载,其他的后续有时间再单独分析。

2. 主流程介绍

2.1 Token相关

这里的Token指的其实是WindowToken,因为 【WindowContainer窗口树】介绍过,WindowState的父节点大部分情况是WindowToken,而且在上一篇看到dump启动应前后的窗口树区别,明确知道WindowStateWindowState是挂载到 ActivityRecord 下的, 而 ActivityRecord 是 WindowToken 的子类。

在分析WindowState的创建和挂载前,需要先给它找到它的父节点 -- WindowToken。这也是 WindowManagerService::addWindow方法中比较靠前执行的逻辑。

这一小节只关心 addWindow 方法中和WindowToken相关的代码

java 复制代码
# WindowManagerService

    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
                ......
                // 1. 定义父窗口
                WindowState parentWindow = null;
                ......
                    // 2. 定义ActivityRecord
                    ActivityRecord activity = null;
                    // 是否为hasParent(当前场景为false)
                    final boolean hasParent = parentWindow != null;
                    // 3. 获取token
                    WindowToken token = displayContent.getWindowToken(
                            hasParent ? parentWindow.mAttrs.token : attrs.token);
                    ......
                    if (token == null) {
                        ......
                        if (hasParent) {
                            // Use existing parent window token for child windows.
                            // 比如PopupWindow在这使用父窗口的token
                            token = parentWindow.mToken;
                        } else if (mWindowContextListenerController.hasListener(windowContextToken)) {
                            ......
                        } else {
                            // 4. WindowToken
                            final 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) {
                        // 转换成ActivityRecord
                        activity = token.asActivityRecord();
                        ......
                    } else if......// 忽略其他各种创建对 token的处理
                    ......
            }

针对注释里的数字,再做更详细的解释:

    1. 首先定义了一个父窗口的变量 parentWindow ,当前是没有父窗口的,比如起了一个popupWindow, 那parentWindow的值就是对应的Window。
    1. 定义了 ActivityRecord,可以看到方法末尾如果 窗口类型是应用类型, 就会将token转换成ActivityRecord。 因为 ActivityRecord 是 WindowToken的子类,asActivityRecord这个方法是定义在窗口容器基类--WindowContainer下的,但是只有 ActivityRecord 重新了这个方法。
    1. 通过 DisplayContent::getWindowToken 方法来获取到token,

首先已经知道parentWindow是没有的,因为当前是Activity下的addWindow,只有子窗口才有parentWindow。那么token的赋值就是后面那句"attrs.token",这个attrs根据传参调用知道是Window的参数,但是之前好像的分析并没有看到对attrs.token的赋值。

但是上一篇 在分析WindowManagerGlobal::addView方法的时候在adjustLayoutParamsForSubWindow 方法的注释上提过一下token,现在仔细看一下这个方法。

csharp 复制代码
# Window
    
    // 应用Token
    private 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。

可以再回到 上一篇2.1.1 小节看一下 setWindowManager 方法

    1. 如果第三步没有获取到token,就需要创建一个 WindowToken 对象进行赋值了, 因为如果没有一个WindowToken那当前方法后续的逻辑就没法执行了, WindowState就没有挂载的父节点了。

    那什么场景下会走到这呢? 目前一直应用窗口也就是Activity是在第三步就拿到了token的, 但是系统窗口,比如状态栏,是需要在这一步创建WindowToken的,而且是根据窗口类型创建的,因为创建出来的WindowToken是需要挂载到窗口树的,所以会根据WindowType来找到自己需要挂载的位置。具体的可以看 WindowContainer窗口层级-3-实例分析下的系统窗口挂载。

2.1.1 WindowToken小结

在WMS模块中这个token非常重要,虽然上面流程已经介绍了token的由来,但是再重新理一遍。 首先我们知道在Activity启动流程的时候AMS会创建ActivityRecord,ActivityRecord的父类的WindowToken,ActivityRecord构成方法的时候就会创建一个匿名token,所以ActivityRecord内部是有一个token的,直接将ActivityRecord理解为token,也没什么问题。

然后在ActivityThread::performLaunchActivity方法调用Activity::attach方法时就将token传递了进去,在Activity::attach通过window::setWindowManager方法将token设置给了Window的成员变量mAppToken。 然后在调用Window::adjustLayoutParamsForSubWindow方法的时候,又将mAppToken设置给了Window的LayoutParams.token。

再回到WMS::addWindow方法中,获取到token后下面有一大堆代码,其实是对token ==null的处理,像系统window,或者子window就会执行,那么当前逻辑,是不需要执行的。

拿到WindowToken后,WindowState才能有挂载的父节点,addWindow方法后面才可以正常执行。

2.2 WindowState的创建

addWindow的流程是要将WindowState挂在到窗口树中,所以肯定是要创建一个WindowState的。

java 复制代码
# WindowManagerService

    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
            ......// WindowToken相关处理
            // 创建WindowState
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
            ......
        }

这里注意几个参数,然后直接看WindowState的构造方法

java 复制代码
# WindowState

    @NonNull WindowToken mToken;
    // The same object as mToken if this is an app window and null for non-app windows.
    // 与mToken相同的对象(如果这是应用程序窗口),而对于非应用程序窗口为null
    // 说人话就是应用窗口才有

    ActivityRecord mActivityRecord;
    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) {
            ......
            mToken = token;
            mActivityRecord = mToken.asActivityRecord();
            ......

            }

创建WindowState有2个重要的参数client,和token。这个client看名字也知道代表着客户端,那就是APP进程了,根据之前的分析知道就是ViewRootImpl的内部类W,然后其也持有着ViewRootImpl的软引用,另一个参数token上面刚刚分析过。

WindowState 以后会经常看到,不过当前只要知道在 WindowManagerService::addWindow 会创建出一个WindowState对象即可。

2.3 WindowState的挂载

arduino 复制代码
# WindowManagerService
    // ViewRootImpl和WindowState的map
    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) {
            ......
            // 窗口已经添加,直接return
            if (mWindowMap.containsKey(client.asBinder())) {
                // 打印log
                ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
            ......// WindowToken相关处理
            ......// WindowState的创建
            // WindowState的挂载
            win.attach();
            // 1. 存进map
            mWindowMap.put(client.asBinder(), win);
            ......
            // 2. 挂载
            win.mToken.addWindow(win);
            ......
        }
    1. 在看挂载前先看一下 mWindowMap这个数据结构,key是一个IBinder,value是 WindowState,这边将新创建的WindowState作为value添加到了map中,前面说过client是应用端ViewRootImpl下的 "W"这个类,也就是说在WMS中应用端的这个ViewRootImpl和为其创建的WindowState已经被记录在mWindowMap中了。

在执行WMS::addWindow方法开始的时候就会尝试通过clent从mWindowMap获取值,如果获取到了说明已经执行过addWindow,值进行return,不执行后面逻辑。

    1. 这里是窗口的挂载,"win.mToken"这里的mToken刚刚看到是创建WindowState的时候传递的token,也就是 ActivityRecord (WindowToken)。

也就是说调用的是ActivityRecord::addWindow方法进行挂载的

typescript 复制代码
# ActivityRecord
    @Override
    void addWindow(WindowState w) {
        super.addWindow(w);
        ......
    }

直接调用其父类方法,ActivityRecord父类是WindowToken

scss 复制代码
# WindowToken
    void addWindow(final WindowState win) {
        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.
            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窗口层级】和【Activity启动流程】的流程就知道现在WindowState已经被添加到层级树中了,挂在到对应的ActivityRecord下。

当然这里需要注意WindowToken::addWindow最终也是调用父类WindowContainer::addChild将WindowState添加到自己的孩子中,这里传递了一个mWindowComparator。

2.3.1 addWindow是顺序 WindowToken下的mWindowComparator

arduino 复制代码
# WindowToken下的mWindowComparator
    private final Comparator<WindowState> mWindowComparator =
            (WindowState newWindow, WindowState existingWindow) -> {
        ......
        return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
    };
    protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
            WindowState existingWindow) {
        // 就是比较两个窗口的mBaseLayer
        return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
    }

这里的newWindow和existingWindow当然是一个当前需要添加进容器的WindowState和上一个存在的WindowState

    1. 对 WindowToken 的子窗口进行比较排序,1和-1表示相对顺序
    1. 返回true,则是1,表示插入在后面。 也就是newWindow的mBaseLayer大于原来的
    1. 返回false,则是-1,表示插入在签名,也就是newWindow的mBaseLayer小于原来的

简单来说就是比较mBaseLayer来判断当前新添加的是放在哪个位置。 正常情况都是按顺添加,也就是后添加的在最上面。

3. 总结

addWindow流程在system_service进程这边主要就是在 WindowManagerService::addWindow 方法处理的,这个方法也是WMS的核心方法。 先是确保能获取到一个WindowToken,然后进行WindowState的创建和挂载。

最后来解释下之前在上一篇留下的疑问,因为在WMS中并没有Window,有的是WindowState,而WMS通过App层传来的token和client创建WindowState的能映射到对应的Activity和Window,以及ViewRootImpl。 所以addWindow流程在应用进程最后只是通过 ViewRootImpl::setView 方法中触发跨进程调用时,传递一个 "W"到system_service进程,WMS那边并不关系应用这边的Window和View。 WMS那边如果有什么事情,只有通过这个"W"就可以和应用进程进行通信了。

【Android 13源码分析】WMS-添加窗口(addWindow)流程-1-应用进程处理

【Android 13源码分析】WMS-添加窗口(addWindow)流程-2-SystemService进程处理

相关推荐
恋猫de小郭25 分钟前
React 和 React Native 不再直接归属 Meta,React 基金会成立
android·前端·ios
bst@微胖子44 分钟前
鸿蒙实现滴滴出行项目之侧边抽屉栏以及权限以及搜索定位功能
android·华为·harmonyos
zcz16071278211 小时前
Docker Compose 搭建 LNMP 环境并部署 WordPress 论坛
android·adb·docker
Pika11 小时前
深入浅出 Compose 测量机制
android·android jetpack·composer
谷哥的小弟11 小时前
Spring Framework源码解析——ApplicationContextAware
spring·源码
木易 士心16 小时前
MPAndroidChart 用法解析和性能优化 - Kotlin & Java 双版本
android·java·kotlin
消失的旧时光-194316 小时前
Kotlin Flow 与“天然背压”(完整示例)
android·开发语言·kotlin
ClassOps16 小时前
Kotlin invoke 函数调用重载
android·开发语言·kotlin
努力学习的小廉16 小时前
初识MYSQL —— 数据类型
android·数据库·mysql
Lei活在当下20 小时前
【业务场景架构实战】7. 多代智能手表适配:Android APP 表盘编辑页的功能驱动设计
android·设计模式·架构