WMS源码解析

0 阅读12分钟

一. WMS

WMS 是 Android 中很重要的一个服务,它是 WindowManager 的管理者,WMS 无论对于应用开发还是 Framework 开发来说都是重要的知识点,其原因是因为 WMS 有很多职责,每个职责都会涉及重要且复杂的系统,这使得 WMS 就像一个十字路口的交通灯一样,没有了这个交通灯,十字路口就无法正常通车。

WMS 的职责简单来说主要有以下几点:

  1. 窗口管理:WMS 是窗口的管理者,它负责窗口的启动、添加和删除。另外窗口的大小和层级也是由 WMS 进行管理的。
  2. 窗口动画:窗口动画由 WMS 的动画子系统来负责,动画子系统的管理者为 WindowAnimator。
  3. 输入系统中转站:通过对窗口的触摸从而产生触摸事件,InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS 是窗口的管理者,它作为输入系统的中转站再合适不过了。
  4. Surface 管理:窗口不具备绘制功能,因此每个窗口都需要有一块 Surface 来供自己绘制,为每个窗口分配 Surface 是由 WMS 来完成的。

WMS的职责可以简单总结为下图: image.png

二. WMS 的重要成员

WMS 的部分成员变量如下,本文源码基于 Android 11.0。

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {

    WindowManagerPolicy mPolicy;
    final ArraySet<Session> mSessions = new ArraySet<>();
    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

    /**
    * Windows that are being resized. Used so we can tell the client about
    * the resize after closing the transaction in which we resized the
    * underlying surface.
    */
    final ArrayList<WindowState> mResizingWindows = new ArrayList<>();

    /**
    * Windows whose animations have ended and now must be removed.
    */
    final ArrayList<WindowState> mPendingRemove = new ArrayList<>();

    /**
    * Used when processing mPendingRemove to avoid working on the original array.
    */
    WindowState[] mPendingRemoveTmp = new WindowState[20];

    /**
    * Windows whose surface should be destroyed.
    */
    final ArrayList<WindowState> mDestroySurface = new ArrayList<>();

    /**
    * Windows with a preserved surface waiting to be destroyed. These windows
    * are going through a surface change. We keep the old surface around until
    * the first frame on the new surface finishes drawing.
    */
    final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>();

    final InputManagerService mInputManager;

    final WindowAnimator mAnimator;

    final H mH = new H();
}

1. mPolicy:WindowManagerPolicy

WindowManagerPolicy(WMP)类型的变量。WindowManagerPolicy 是窗口管理策略的接口类,用来定义一个窗口策略所要遵循的通用规范,并提供了 WindowManager 所有的特定的 UI 行为。它的具体实现类为 PhoneWindowManager ,这个实现类在 WMS 创建时被创建。WMP 允许定制窗口层级和特殊窗口类型以及关键的调度和布局。

2. mSessions:ArraySet<Session>

ArraySet<Session> 类型的变量。它主要用于进程间通信,其他的应用程序进程想要和 WMS 进程进行通信就需要经过 Session ,并且每个应用程序进程都会对应一个 Session ,WMS 保存这些 Session 用来记录所有向 WMS 提出窗口管理服务的客户端。

3. mWindowMap:HashMap<IBinder, WindowState>

mWindowMap 是一个 HashMap,key 的类型为 IBinder ,value 的类型为 WindowState 。WindowState 用于保存窗口的信息,在 WMS 中它用来描述一个窗口。综上得出结论,mWindowMap 就是用来保存 WMS 中各种窗口的集合。

4.mResizingWindows:ArrayList<WindowState>

mResizingWindows 是用来存储正在调整大小的窗口的列表的。与 mResizingWindows 类似的还有 mPendingRemove、mDestroySurface 和 mDestroyPreservedSurface 等,其中 mPendingRemove 是在内存耗尽时设置的,里面存有需要强制删除的窗口,mDestroySurface 里面存有需要被销毁的Surface,mDestroyPreservedSurface 里面存有窗口需要保存的等待销毁的 Surface,为什么窗口要保存这些 Surface ?这是因为当窗口经历 Surface 变化时,窗口需要一直保持旧的 Surface,直到新 Surface 的第一帧绘制完成。

5.mAnimator:WindowAnimator

mAnimator 是 WindowAnimator 类型的变量,用于管理窗口的动画以及特效动画。

6.mH:H

mH 是 H 类型的变量,系统的 Handler 类,用于将任务加入到主线程的消息队列中,这样代码逻辑就会在主线程中执行。

7.mInputManager:InputManagerService

InputManagerService 类型的变量,输入系统的管理者。InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS 是窗口的管理者,因此,WMS “理所应当” 的成为了输入系统的中转站。

还需要理解的几个概念:

WindowToken

  • 可以理解为窗口令牌,当应用程序想要向 WMS 申请新创建一个窗口,则需要向 WMS 出示有效的 WindowToken。WindowToken 会将相同组件(比如同一个Acitivity)的窗口(WindowState)集合在一起,方便管理。

WindowState:

  • WindowState 表示一个窗口的所有属性,且存在于 WMS 端,所以它是 WMS 中事实上的窗口。App 端的一个 Window,在 WMS 端就会有一个 WindowState 。

三. Window 的添加过程(WMS 处理的部分)

对 Window 的操作分为两大部分,一部分是 WindowManger 的处理,在 Window和WindowManager源码解析 中已经分析过,另一部分是 WMS 的处理,这里我们来分析 WMS 处理的部分。Window 的添加过程都会调用 WMS 的 addWindow() 方法,由于这个方法代码逻辑比较多,这里分成3个部分来分析。

3.1 窗口检查

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {

    public int addWindow(Session session, IWindow client, int seq,
                         LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
                         Rect outContentInsets, Rect outStableInsets,
                         DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
                         InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
                         int requestUserId) {
        Arrays.fill(outActiveControls, null);
        int[] appOp = new int[1];
        final boolean isRoundedCornerOverlay = (attrs.privateFlags
                & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
        // 调用 WindowManagerPolicy 的 checkAddPermission() 方法来检查权限 
        int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
                appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
        ...
        synchronized (mGlobalLock) {
            ...
            // 通过 DisplayId 来获得窗口要添加到哪个 DisplayContent 上
            final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);

            // 如果没有找到 DisplayContent,返回 WindowManagerGlobal.ADD_INVALID_DISPLAY
            if (displayContent == null) {
                ProtoLog.w(WM_ERROR, "Attempted to add window to a display that does "
                        + "not exist: %d. Aborting.", displayId);
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            if (!displayContent.hasAccess(session.mUid)) {
                ProtoLog.w(WM_ERROR,
                        "Attempted to add window to a display for which the application "
                                + "does not have access: %d. Aborting.", displayId);
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            if (mWindowMap.containsKey(client.asBinder())) {
                ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

            // type 在 FIRST_SUB_WINDOW 与 LAST_SUB_WINDOW 之间(1000~1999),说明是子窗口
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                // attrs.token 是 IBinder 类型的对象
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a window: "
                            + "%s. Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    ProtoLog.w(WM_ERROR, "Attempted to add window with token that is a sub-window: "
                            + "%s. Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }
        }
        ...
        return res;
    }
}

WMS 的 addWindow() 的返回值表示的是 addWindow() 的各种状态,是一个 int 值,定义在 WindowManagerGlobal 中。在 addWindow() 方法中先调用 WindowManagerPolicy 的 checkAddPermission() 方法来检查权限,具体在 PhoneWindowManager 的 checkAddPermission() 方法中实现,如果没有权限就不会执行后续的代码逻辑。然后通过 DisplayId 来获得窗口要添加到哪个 DisplayContent 上,如果没有找到 DisplayContent ,返回 ADD_INVALID_DISPLAY,其中 DisplayContent 是用来描述一块屏幕的。其中 type 表示窗口的类型,如果介于 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之间,说明这个窗口是一个子窗口,要拿到父窗口的 WindowState,attrs.token 是 IBinder 类型,windowForClientLocked() 方法会根据 attrs.token 作为 key 值从 mWindowMap 中得到该子窗口的父窗口。接着对父窗口进行判断,如果父窗口的 WindowState 为 null 或者父窗口的 type 也是子窗口的范围,会返回错误状态。

3.2 WindowToken 相关处理

// 通过 DisplayContent 尝试获取 WindowToken 
WindowToken token = displayContent.getWindowToken(
        hasParent ? parentWindow.mAttrs.token : attrs.token);
// If this is a child window, we want to apply the same type checking rules as the
// parent window type.
// 如果有父窗口,就将父窗口的 type 赋值给 rootType,如果没有就将当前窗口的 type 赋值给 rootType
final int rootType = hasParent ? parentWindow.mAttrs.type : type;

boolean addToastWindowRequiresToken = false;

// 如果 token 为 null
if (token == null) {
    // 使用 unprivilegedAppCanCreateTokenWith() 方法对 rootType 和 type 进行检查
    if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type,
            rootType, attrs.token, attrs.packageName)) {
        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
    }
    final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
    // 创建 WindowToken,第 4 个参数为 false ,表示这个 WindowToken 是隐式创建的
    token = new WindowToken(this, binder, type, false, displayContent,
            session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
} else if (rootType >= FIRST_APPLICATION_WINDOW
        && rootType <= LAST_APPLICATION_WINDOW) {
    activity = token.asActivityRecord();
    if (activity == null) {
        ProtoLog.w(WM_ERROR, "Attempted to add window with non-application token "
                + ".%s Aborting.", token);
        return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
    } else if (activity.getParent() == null) {
        ProtoLog.w(WM_ERROR, "Attempted to add window with exiting application token "
                + ".%s Aborting.", token);
        return WindowManagerGlobal.ADD_APP_EXITING;
    } else if (type == TYPE_APPLICATION_STARTING && activity.startingWindow != null) {
        ProtoLog.w(WM_ERROR,
                "Attempted to add starting window to token with already existing"
                        + " starting window");
        return WindowManagerGlobal.ADD_DUPLICATE_ADD;
    }
} 
...

这里先通过 DisplayContent 的 getWindowToken() 方法尝试获取 WindowToken ,如果有父窗口,就将父窗口的 type 赋值给 rootType ,如果没有,就将当前窗口的 type 赋值给 rootType 。接下来如果 token 为 null ,使用 unprivilegedAppCanCreateTokenWith() 方法对 rootType 和 type 进行检查,里面检查如果 rootType 值等于 TYPE_INPUT_METHOD、TYPE_WALLPAPER 等值时,则返回 ADD_BAD_APP_TOKEN,说明 rootType 值等于 TYPE_INPUT_METHOD、TYPE_WALLPAPER 等值时是不允许 WindowToken 为 null 的。如果检查通过了,会创建 WidowToken,这说明当我们添加窗口时可以不向 WMS 提供 WindowToken,前提是 rootType 和 type 的值不为前面条件判断筛选的值。WindowToken 隐式和显式的创建肯定是要加以区分的,创建 WidowToken 传入的第 4 个参数为 false 就表示这个 WindowToken 是隐式创建的。

接下来的代码逻辑就是 WindowToken 不为 null 的情况,根据 rootType 和 type 的值进行判断。先判断如果窗口为应用程序窗口,通过 WindowToken 的 asActivityRecord() 方法拿到 ActivityRecord,然后根据 ActivityRecord 进行后续的判断,返回不同的状态码。

3.3 WindowState 的创建和处理

// 创建 WindowState,它存有窗口的所有状态信息,在 WMS 中它代表一个窗口,
// this 指的是 WMS,client 指的是 IWindow, token 指的是 WindowToken。
final WindowState win = new WindowState(this, session, client, token, parentWindow,
        appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
        session.mCanAddInternalSystemWindow);

// 判断请求添加窗口的客户端是否已经死亡 
if (win.mDeathRecipient == null) {
    // Client has apparently died, so there is no reason to
    // continue.
    ProtoLog.w(WM_ERROR, "Adding window client %s"
            + " that is dead, aborting.", client.asBinder());
    return WindowManagerGlobal.ADD_APP_EXITING;
}

// 判断窗口的 DisplayContent 是否为 null
if (win.getDisplayContent() == null) {
    ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
    return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}

final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
// 根据窗口的 type 对窗口的 LayoutParams 的一些成员变量进行修改
displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid);
// 根据窗口的 type 检查是否可以把这个窗口添加到系统中
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != WindowManagerGlobal.ADD_OKAY) {
    return res;
}
...
win.attach();
//将 WindowState 添加到 mWindowMap 中
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();

final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(),
        UserHandle.getUserId(win.getOwningUid()));
win.setHiddenWhileSuspended(suspended);

final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);

final ActivityRecord tokenActivity = token.asActivityRecord();
if (type == TYPE_APPLICATION_STARTING && tokenActivity != null) {
    tokenActivity.startingWindow = win;
    ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
            activity, win);
}

boolean imMayMove = true;
//将 WindowState 添加到该 WindowState 对应的 WindowToken 中
win.mToken.addWindow(win);
displayPolicy.addWindowLw(win, attrs);
if (type == TYPE_INPUT_METHOD) {
    displayContent.setInputMethodWindowLocked(win);
    imMayMove = false;
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
    displayContent.computeImeTarget(true /* updateImeTarget */);
    imMayMove = false;
} else {
    if (type == TYPE_WALLPAPER) {
        displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
        displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
    } else if ((attrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
        displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
    } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
        // If there is currently a wallpaper being shown, and
        // the base layer of the new window is below the current
        // layer of the target window, then adjust the wallpaper.
        // This is to avoid a new window being placed between the
        // wallpaper and its target.
        displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
    }
}
...

先创建了 WindowState,它存有窗口的所有的状态信息,在 WMS 中它代表一个窗口。在创建 WindowState 传入的参数中,this 指的是 WMS,client 指的是 IWindow,IWindow 会将 WMS 中窗口管理的操作回调给 ViewRootImpl,token 指的是 WindowToken ,紧接着分别判断请求添加窗口的客户端是否已经死亡、窗口的 DisplayContent 是否为 null,如果是则不会再执行下面的代码逻辑。然后调用了 DisplayPolicy 的 adiustWindowParamsLw() 方法,此方法会根据窗口的 type 对窗口的 LayoutParams 的一些成员变量进行修改。然后调用 DisplayPolicy 的 validateAddingWindowLw() 方法,用于检查是否可以把这个窗口添加到系统中。接着将 WindowState 添加到 mWindowMap 中,然后将 WindowState 添加到该 WindowState 对应的 WindowToken 中(实际是保存在 WndowToken 的父类 WindowContainer 中),这样 WindowToken 就包含了同一个组件的 WindowState。

3.4 addWindow()方法总结

addWindow() 方法分了 3 个部分来进行讲解,主要就是做了下面 4 件事:

  1. 对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会再执行下面的代码逻辑。
  2. WindowToken 相关的处理,比如有的窗口类型需要提供 WindowToken ,没有提供的话就不会执行下面的代码逻辑,有的窗口类型则可以由 WMS 隐式创建 WindowToken。
  3. WindowState的创建和相关处理,将 WindowToken 和 WindowState 相关联。
  4. 创建和配置 DisplayContent,完成窗口添加到系统前的准备工作。

四. Window 的删除过程

Window 的删除过程会调用 WMS 的 removeWindow() 方法:

void removeWindow(Session session, IWindow client) {
    synchronized (mGlobalLock) {
        WindowState win = windowForClientLocked(session, client, false); // 1
        if (win != null) {
            win.removeIfPossible(); // 2
            return;
        }

        // Remove embedded window map if the token belongs to an embedded window
        mEmbeddedWindowController.remove(client);
    }
}

在注释 1 处获取 Window 对应的 WindowState,WindowState 用于保存窗口的信息,在 WMS 中它用来描述一个窗口。接着在注释 2 处调用 WindowState 的 removelfPossible() 方法,如下所示:

void removeIfPossible() {
    super.removeIfPossible();
    removeIfPossible(false /*keepVisibleDeadWindow*/);
    immediatelyNotifyBlastSync();
}

里面接着又调用了带参数的 removeIfPossible() 方法:

private void removeIfPossible(boolean keepVisibleDeadWindow) {
    // 条件判断过滤,满足其中一个条件就会 return,推迟删除操作
    ...
    removeImmediately(); // 1
    // Removing a visible window will effect the computed orientation
    // So just update orientation if needed.
    if (wasVisible) {
        final DisplayContent displayContent = getDisplayContent();
        if (displayContent.updateOrientation()) {
            displayContent.sendNewConfiguration();
        }
    }
    mWmService.updateFocusedWindowLocked(isFocused()
                    ? UPDATE_FOCUS_REMOVING_FOCUS
                    : UPDATE_FOCUS_NORMAL,
            true /*updateInputWindows*/);
}

removelfPossible() 方法和它的名字一样,并不是直接执行删除操作的,而是进行多个条件判断过滤,满足其中一个条件就会 return,推迟删除操作。比如 View 正在运行一个动画这时就得推迟删除操作,直到动画完成。通过这些条件判断过滤就会执行注释 1 处的 removeImmediately() 方法:

void removeImmediately() {
    super.removeImmediately();

    if (mRemoved) { // 1
        // Nothing to do.
        ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                "WS.removeImmediately: %s Already removed...", this);
        return;
    }

    mRemoved = true; 

    ...
    dc.getDisplayPolicy().removeWindowLw(this); // 2

    disposeInputChannel();

    mWinAnimator.destroyDeferredSurfaceLocked();
    mWinAnimator.destroySurfaceLocked();
    mSession.windowRemovedLocked(); // 3
    try {
        mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
    } catch (RuntimeException e) {
        // Ignore if it has already been removed (usually because
        // we are doing this as part of processing a death note.)
    }

    mWmService.postWindowRemoveCleanupLocked(this); // 4
}

removelmmediately() 方法如同它的名字一样,用于立即进行删除操作。在注释 1 处的 mRemoved 为 true 意味着正在执行删除 Window 操作。在注释 2 处如果当前要删除的 Window 是 StatusBar 或者 NavigationBar,就会将这个 Window 从对应的控制器中删除。在注释 3 处将对应的 Session 从 WMS 的 ArraySet<Session> 类型的参数 mSessions 中删除并清除 Session 对应的 SurfaceSession 资源(SurfaceSession 是 SurfaceFlinger 的一个连接,通过这个连接可以创建 1 个或者多个 Surface 并渲染到屏幕上)。在注释 4 处调用了 WMS 的 postWindowRemoveCleanupLocked() 方法用于对 View 进行一些集中的清理工作。