【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进程处理

相关推荐
Reese_Cool1 小时前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言
平凡シンプル1 小时前
安卓 uniapp跨端开发
android·uni-app
elina80131 小时前
安卓实现导入Excel文件
android·excel
严文文-Chris2 小时前
【设计模式-享元】
android·java·设计模式
趋势大仙2 小时前
SQLiteDatabase insert or replace数据不生效
android·数据库
DS小龙哥2 小时前
QT For Android开发-打开PPT文件
android·qt·powerpoint
试行3 小时前
Android实现自定义下拉列表绑定数据
android·java
Dingdangr8 小时前
Android中的Intent的作用
android
技术无疆8 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP8 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑