Android WMS——WMS窗口添加(十)

Android 的 WMS(Window Manager Service)是一个关键组件,负责管理窗口的创建、显示、布局和交互等。Window 的操作有两大部分,一部分是 WindowManager 来处理,一部分是 WMS 来处理,如下图所示:

WindowManager 中,通过 WindowManagerGlobal 创建 ViewRootImpl ,也就是 View 的根。在 ViewRootImpl 中完成对 View 的绘制等操作,然后通过 IPC 获取到 Session,最终通过 WMS 来进行处理。WindowManager 部分的管理流程前面已经介绍,这里我们来看一下 WMS 对 Window 的管理。

一、调用流程

1、WM到WMS

我们都知道 Window 的添加最后是通过 ViewRootImpl.addTodisplay 方法来完成的,我们先来看一下:

ViewRootImpl

源码位置:/frameworks/base/core/java/android/view/ViewRootImpl.java

java 复制代码
final IWindowSession mWindowSession;
 
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
    synchronized (this) {
        if (mView == null) {
            ......
            try {
                ......
                res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), userId,
                        mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                        mTempControls);
            }
            ......
        }
    }
}

这里调用了 mWindowSession.addToDisplayAsUser 来完成最后的添加,可以看到 IWindowSession 是一个接口类,真正实现该接口的是 Session 类。

Session

源码位置:/frameworks/base/services/core/java/com/android/server/wm/Session.java

java 复制代码
final WindowManagerService mService;
 
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
             int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
    return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
                 requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
}

可以看到,最终是通过 WMS 来完成添加的。需要注意的是,WMS 并不关心 View 的具体内容,他只关心各个应用显示的界面大小、层级值等,这些数据到包含在 WindowManager.LayoutParams 中。也就是上面的 atrs 属性。

addWindow 的第二个参数是一个 IWindow 类型,这是 App 暴露给 WMS 的抽象实例,在 ViewRootImp 中实例化,与 ViewRootImpl 一一对应,同时也是 WMS 向 App 端发送消息的 Binder 通道。

二、WMS窗口添加

WindowManagerService

源码位置:/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

java 复制代码
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
              int displayId, int requestUserId, InsetsState requestedVisibility,
              InputChannel outInputChannel, InsetsState outInsetsState,
              InsetsSourceControl[] outActiveControls) {
    Arrays.fill(outActiveControls, null);
    int[] appOp = new int[1];
    final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
    // 1.检查权限,mPolicy的实现类是PhoneWindowManager。
    int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName, appOp);
    if (res != ADD_OKAY) {
        return res;
    }
    
    ......
    synchronized (mGlobalLock) {
        ......
        // 2.通过displayId获取Window要添加到哪个DisplayContent。
        final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
        if (displayContent == null) {
            // 没有找到对应的显示屏幕
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }

        ......
        // 3.判断type的窗口类型(100-1999),如果是子类型,必须要有父窗口,并且父窗口不能是子窗口类型。
        if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
            parentWindow = windowForClientLocked(null, attrs.token, false);
            if (parentWindow == null) {
                // 试图添加带有非窗口令牌的窗口
                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
            }
            if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                // 试图添加带有子标记的窗口
                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
            }
        }

        ......
        ActivityRecord activity = null;
        final boolean hasParent = parentWindow != null;
        
        // 4.对子窗口使用与父窗口使用的令牌,因此我们可以对它们应用相同的策略。
        WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
        // 如果这是一个子窗口,与父窗口类型相同的检查规则。
        final int rootType = hasParent ? parentWindow.mAttrs.type : type;

        boolean addToastWindowRequiresToken = false;

        // 5. token为null。
        if (token == null) {
            if (hasParent) {
                // 对子窗口使用现有的父窗口令牌。
                token = parentWindow.mToken;
            } else if (mWindowContextListenerController.hasListener(windowContextToken)) {
                // 如果用户提供的话,尊重窗口上下文令牌
                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 {
                // 6.且不是应用窗口或者是其他类型的窗口,则窗口就是系统类型(例如 Toast)。
                // 进行隐式创建 WindowToken,这说明我们添加窗口时是可以不向WMS提供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) {
            // 7、判断是否为应用窗口,如果是,将WindowToken转换为应用程序窗口的ActivityRecord。
            activity = token.asActivityRecord();
            if (activity == null) {
                // 试图添加带有非应用程序令牌的窗口
                return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
            } else if (activity.getParent() == null) {
                // 试图添加具有退出应用程序的窗口
                return WindowManagerGlobal.ADD_APP_EXITING;
            } else if (type == TYPE_APPLICATION_STARTING) {
                if (activity.mStartingWindow != null) {
                    // 试图用已经存在的开始窗口向令牌添加开始窗口
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
                if (activity.mStartingData == null) {
                    // 试图向令牌添加起始窗口,但已被清除
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
            }
        } else if (rootType == TYPE_INPUT_METHOD) {
            if (token.windowType != TYPE_INPUT_METHOD) {
                // 试图添加带有错误令牌的输入法窗口
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        }

        ......
        // 8.创建WindowState,保存窗口的所有状态信息,在WMS中,WindowState与窗口是一一对应的关系。
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                      appOp[0], attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow);
        
        // 9.判断请求添加窗口的客户端是否已经死亡,如果死亡则不会执行下面逻辑。
        if (win.mDeathRecipient == null) {
            return WindowManagerGlobal.ADD_APP_EXITING;
        }
        if (win.getDisplayContent() == null) {
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }

        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
        // 10.根据窗口的type类型对窗口的LayoutParams的一些成员变量进行修改。
        displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
        win.updateRequestedVisibility(requestedVisibility);

        // 11.准备将窗口添加到系统中
        res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
        if (res != ADD_OKAY) {
            return res;
        }
        
        ......
        // 12.将WindowState添加到mWindowMap中,mWindowMap是各种窗口的集合。
        mWindowMap.put(client.asBinder(), win);
        ......
        boolean imMayMove = true;
        // 13.添加窗口。将WindowState添加到对应的WindowToken中(实际上就是保存在WindowToken的父类 WindowContainer),这样WindowToken就包含了相同组件的WindowState。
        win.mToken.addWindow(win);
    }
    return res;
}

WMS 的 addWindow 方法返回的是 addWindow 的各种状态,例如添加成功、失败、无效的 display 等,这些状态定义在 WindowManagerGloabl 中 。

通过上面的流程,App 到 WMS 注册窗口的流程就完了,WMS 为窗口创建了用来描述状态的 WindowState,接下来就会为新建的窗口显示次序,然后再去申请 Surface,才算是真正的分配了窗口。

这里对 WMS 的 addWindow 流程做一个总结 :

  • 首先检查权限

  • 接着从 mRoot(RootWindowContainer)中获取 DisplayContent ,如果没有就会根据 displayId 创建一个新的 DisplayContent

  • 接着就是 type 类型的判断,如果是子类型,就必须要获取到他的父窗口,

  • 接着使用 DisplayContent 获取当前或者父窗口获取 token,如果为 null 就排除子窗口和其他的窗口,剩下的就是可以不用携带 token 的窗口,WMS 会隐式的创建窗口 token。如果不等于 null 就判断是应用窗口就将 token 转为 ActivityRecord,后面还有一大堆窗口判断,只要是不满足就直接 return。

  • 类型判断完成后,就会创建 WindowState,并且传入 WMS、IWindow、token 等。WindowState 里面保存了窗口的所有信息。WindowState 与窗口一一对应。

  • 接着就执行调用了 WindowState 的 attache 、initAppOpsState 等方法。WindowState 创建完成后就会被添加到 mWindowMap 中,可以 IWindow 的 Binder 为 key,WindowState 为 value 添加进去。

  • 最后就是 win.mToken.addWindow(win) ,然后将 WindowState 添加到 WindowToken 中。因为 WindowToken 是可以复用的,所以这里的关系就是,每个 WindowToken 都会保存对应的 WindowState,而每个 WindowState 也都会都持有 WindowToekn。

相关推荐
带电的小王2 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡2 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道3 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库4 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道4 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe4 小时前
Android Hook - 动态加载so库
android
居居飒5 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He8 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗8 小时前
Android笔试面试题AI答之Android基础(1)
android
qq_3975623110 小时前
android studio更改应用图片,和应用名字。
android·ide·android studio