Android 系统启动系列
Android 深入四大组件系列
Android 应用进程启动过程系列
Android 解析 WindowManager 系列
前言
在本系列的上一篇文章中, 我们学习了 WMS 的诞生, WMS 被创建后, 它的重要的成员有哪些? Window 添加过程的 WMS 部分做了什么呢? 这篇文章会给你解答
1.WMS 的重要成员
所谓 WMS 的重要成员是指 WMS 中的重要的成员变量, 如下所示
- frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
- final WindowManagerPolicy mPolicy;
- final IActivityManager mActivityManager;
- final ActivityManagerInternal mAmInternal;
- final AppOpsManager mAppOps;
- final DisplaySettings mDisplaySettings;
- ...
- final ArraySet mSessions = new ArraySet<>();
- final WindowHashMap mWindowMap = new WindowHashMap();
- final ArrayList mFinishedStarting = new ArrayList<>();
- final ArrayList mFinishedEarlyAnim = new ArrayList<>();
- final ArrayList mWindowReplacementTimeouts = new ArrayList<>();
- final ArrayList mResizingWindows = new ArrayList<>();
- final ArrayList mPendingRemove = new ArrayList<>();
- WindowState[] mPendingRemoveTmp = new WindowState[20];
- final ArrayList mDestroySurface = new ArrayList<>();
- final ArrayList mDestroyPreservedSurface = new ArrayList<>();
- ...
- final H mH = new H();
- ...
- final WindowAnimator mAnimator;
- ...
- final InputManagerService mInputManager
这里列出了 WMS 的部分成员变量, 下面分别对它们进行简单的介绍
mPolicy:WindowManagerPolicy
WindowManagerPolicy(WMP)类型的变量 WindowManagerPolicy 是窗口管理策略的接口类, 用来定义一个窗口策略所要遵循的通用规范, 并提供了 WindowManager 所有的特定的 UI 行为它的具体实现类为 PhoneWindowManager, 这个实现类在 WMS 创建时被创建 WMP 允许定制窗口层级和特殊窗口类型以及关键的调度和布局
mSessions:ArraySet
ArraySet 类型的变量, 元素类型为 Session 在 Android 解析 WindowManager(三)Window 的添加过程这篇文章中我提到过 Session, 它主要用于进程间通信, 其他的应用程序进程想要和 WMS 进程进行通信就需要经过 Session, 并且每个应用程序进程都会对应一个 Session,WMS 保存这些 Session 用来记录所有向 WMS 提出窗口管理服务的客户端
mWindowMap:WindowHashMap
WindowHashMap 类型的变量, WindowHashMap 继承了 HashMap, 它限制了 HashMap 的 key 值的类型为 IBinder,value 值的类型为 WindowStateWindowState 用于保存窗口的信息, 在 WMS 中它用来描述一个窗口综上得出结论, mWindowMap 就是用来保存 WMS 中各种窗口的集合
mFinishedStarting:ArrayList
ArrayList 类型的变量, 元素类型为 AppWindowToken, 它是 WindowToken 的子类要想理解 mFinishedStarting 的含义, 需要先了解 WindowToken 是什么 WindowToken 主要有两个作用:
可以理解为窗口令牌, 当应用程序想要向 WMS 申请新创建一个窗口, 则需要向 WMS 出示有效的 WindowTokenAppWindowToken 作为 WindowToken 的子类, 主要用来描述应用程序的 WindowToken 结构,
应用程序中每个 Activity 都对应一个 AppWindowToken
WindowToken 会将相同组件 (比如 Acitivity) 的窗口 (WindowState) 集合在一起, 方便管理
mFinishedStarting 就是用于存储已经完成启动的应用程序窗口 (比如 Acitivity) 的 AppWindowToken 的列表
除了 mFinishedStarting, 还有类似的 mFinishedEarlyAnim 和 mWindowReplacementTimeouts, 其中 mFinishedEarlyAnim 存储了已经完成窗口绘制并且不需要展示任何已保存 surface 的应用程序窗口的 AppWindowTokenmWindowReplacementTimeout 存储了等待更换的应用程序窗口的 AppWindowToken, 如果更换不及时, 旧窗口就需要被处理
mResizingWindows:ArrayList
ArrayList 类型的变量, 元素类型为 WindowState
mResizingWindows 是用来存储正在调整大小的窗口的列表与 mResizingWindows 类似的还有 mPendingRemovemDestroySurface 和 mDestroyPreservedSurface 等等其中 mPendingRemove 是在内存耗尽时设置的, 里面存有需要强制删除的窗口 mDestroySurface 里面存有需要被 Destroy 的 SurfacemDestroyPreservedSurface 里面存有窗口需要保存的等待销毁的 Surface, 为什么窗口要保存这些 Surface? 这是因为当窗口经历 Surface 变化时, 窗口需要一直保持旧 Surface, 直到新 Surface 的第一帧绘制完成
mAnimator:WindowAnimator
WindowAnimator 类型的变量, 用于管理窗口的动画以及特效动画
mH:H
H 类型的变量, 系统的 Handler 类, 用于将任务加入到主线程的消息队列中, 这样代码逻辑就会在主线程中执行
mInputManager:InputManagerService
InputManagerService 类型的变量, 输入系统的管理者 InputManagerService(IMS)会对触摸事件进行处理, 它会寻找一个最合适的窗口来处理触摸反馈信息, WMS 是窗口的管理者, 因此, WMS 理所应当的成为了输入系统的中转站, WMS 包含了 IMS 的引用不足为怪
2.Window 的添加过程(WMS 部分)
我们知道 Window 的操作分为两大部分, 一部分是 WindowManager 处理部分, 另一部分是 WMS 处理部分, 如下所示
在 Android 解析 WindowManager(三)Window 的添加过程这篇文章中, 我讲解了 Window 的添加过程的 WindowManager 处理部分, 这一篇文章我们接着来学习 Window 的添加过程的 WMS 部分
无论是系统窗口还是 Activity, 它们的 Window 的添加过程都会调用 WMS 的 addWindow 方法, 由于这个方法代码逻辑比较多, 这里分为 3 个部分来阅读
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
addWindow 方法 part1
- public int addWindow(Session session, IWindow client, int seq,
- WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
- Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
- InputChannel outInputChannel) {
- int[] appOp = new int[1];
- int res = mPolicy.checkAddPermission(attrs, appOp);//1
- if (res != WindowManagerGlobal.ADD_OKAY) {
- return res;
- }
- ...
- synchronized(mWindowMap) {
- if (!mDisplayReady) {
- throw new IllegalStateException("Display has not been initialialized");
- }
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);//2
- if (displayContent == null) {
- Slog.w(TAG_WM, "Attempted to add window to a display that does not exist:"
- + displayId + ". Aborting.");
- return WindowManagerGlobal.ADD_INVALID_DISPLAY;
- }
- ...
- if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {//3
- parentWindow = windowForClientLocked(null, attrs.token, false);//4
- if (parentWindow == null) {
- Slog.w(TAG_WM, "Attempted to add window with token that is not a window:"
- + attrs.token + ". Aborting.");
- return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
- }
- if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
- && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
- Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window:"
- + attrs.token + ". Aborting.");
- return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
- }
- }
- ...
- }
- ...
- }
WMS 的 addWindow 返回的是 addWindow 的各种状态, 比如添加 Window 成功, 无效的 display 等等, 这些状态被定义在 WindowManagerGlobal 中
注释 1 处根据 Window 的属性, 调用 WMP 的 checkAddPermission 方法来检查权限, 具体的实现在 PhoneWindowManager 的 checkAddPermission 方法中, 如果没有权限则不会执行后续的代码逻辑注释 2 处通过 displayId 来获得窗口要添加到哪个 DisplayContent 上, 如果没有找到 DisplayContent, 则返回 WindowManagerGlobal.ADD_INVALID_DISPLAY 这一状态, 其中 DisplayContent 用来描述一块屏幕注释 3 处, type 代表一个窗口的类型, 它的数值介于 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之间(1000~1999), 这个数值定义在 WindowManager 中, 说明这个窗口是一个子窗口, 不了解窗口类型取值范围的请阅读 Android 解析 WindowManager(二)Window 的属性这篇文章注释 4 处, attrs.token 是 IBinder 类型的对象, windowForClientLocked 方法内部会根据 attrs.token 作为 key 值从 mWindowMap 中得到该子窗口的父窗口接着对父窗口进行判断, 如果父窗口为 null 或者 type 的取值范围不正确则会返回错误的状态
addWindow 方法 part2
- ...
- AppWindowToken atoken = null;
- final boolean hasParent = parentWindow != null;
- WindowToken token = displayContent.getWindowToken(
- hasParent ? parentWindow.mAttrs.token : attrs.token);//1
- final int rootType = hasParent ? parentWindow.mAttrs.type : type;//2
- boolean addToastWindowRequiresToken = false;
- if (token == null) {
- if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
- Slog.w(TAG_WM, "Attempted to add application window with unknown token"
- + attrs.token + ". Aborting.");
- return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
- }
- if (rootType == TYPE_INPUT_METHOD) {
- Slog.w(TAG_WM, "Attempted to add input method window with unknown token"
- + attrs.token + ". Aborting.");
- return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
- }
- if (rootType == TYPE_VOICE_INTERACTION) {
- Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token"
- + attrs.token + ". Aborting.");
- return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
- }
- if (rootType == TYPE_WALLPAPER) {
- Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token"
- + attrs.token + ". Aborting.");
- return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
- }
- ...
- if (type == TYPE_TOAST) {
- // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
- if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
- parentWindow)) {
- Slog.w(TAG_WM, "Attempted to add a toast window with unknown token"
- + attrs.token + ". Aborting.");
- return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
- }
- }
- final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
- token = new WindowToken(this, binder, type, false, displayContent,
- session.mCanAddInternalSystemWindow);//3
- } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {//4
- atoken = token.asAppWindowToken();//5
- if (atoken == null) {
- Slog.w(TAG_WM, "Attempted to add window with non-application token"
- + token + ". Aborting.");
- return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
- } else if (atoken.removed) {
- Slog.w(TAG_WM, "Attempted to add window with exiting application token"
- + token + ". Aborting.");
- return WindowManagerGlobal.ADD_APP_EXITING;
- }
- } else if (rootType == TYPE_INPUT_METHOD) {
- if (token.windowType != TYPE_INPUT_METHOD) {
- Slog.w(TAG_WM, "Attempted to add input method window with bad token"
- + attrs.token + ". Aborting.");
- return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
- }
- }
- ...
注释 1 处通过 displayContent 的 getWindowToken 方法来得到 WindowToken 注释 2 处, 如果有父窗口就将父窗口的 type 值赋值给 rootType, 如果没有将当前窗口的 type 值赋值给 rootType 接下来如果 WindowToken 为 null, 则根据 rootType 或者 type 的值进行区分判断, 如果 rootType 值等于 TYPE_INPUT_METHODTYPE_WALLPAPER 等值时, 则返回状态值 WindowManagerGlobal.ADD_BAD_APP_TOKEN, 说明 rootType 值等于 TYPE_INPUT_METHODTYPE_WALLPAPER 等值时是不允许 WindowToken 为 null 的通过多次的条件判断筛选, 最后会在注释 3 处隐式创建 WindowToken, 这说明当我们添加窗口时是可以不向 WMS 提供 WindowToken 的, 前提是 rootType 和 type 的值不为前面条件判断筛选的值 WindowToken 隐式和显式的创建肯定是要加以区分的, 注释 3 处的第 4 个参数为 false 就代表这个 WindowToken 是隐式创建的接下来的代码逻辑就是 WindowToken 不为 null 的情况, 根据 rootType 和 type 的值进行判断, 比如在注释 4 处判断如果窗口为应用程序窗口, 在注释 5 处会将 WindowToken 转换为专门针对应用程序窗口的 AppWindowToken, 然后根据 AppWindowToken 的值进行后续的判断
addWindow 方法 part3
- ...
- final WindowState win = new WindowState(this, session, client, token, parentWindow,
- appOp[0], seq, attrs, viewVisibility, session.mUid,
- session.mCanAddInternalSystemWindow);//1
- if (win.mDeathRecipient == null) {//2
- // Client has apparently died, so there is no reason to
- // continue.
- Slog.w(TAG_WM, "Adding window client" + client.asBinder()
- + "that is dead, aborting.");
- return WindowManagerGlobal.ADD_APP_EXITING;
- }
- if (win.getDisplayContent() == null) {//3
- Slog.w(TAG_WM, "Adding window to Display that has been removed.");
- return WindowManagerGlobal.ADD_INVALID_DISPLAY;
- }
- mPolicy.adjustWindowParamsLw(win.mAttrs);//4
- win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
- res = mPolicy.prepareAddWindowLw(win, attrs);//5
- ...
- win.attach();
- mWindowMap.put(client.asBinder(), win);//6
- if (win.mAppOp != AppOpsManager.OP_NONE) {
- int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(),
- win.getOwningPackage());
- if ((startOpResult != AppOpsManager.MODE_ALLOWED) &&
- (startOpResult != AppOpsManager.MODE_DEFAULT)) {
- win.setAppOpVisibilityLw(false);
- }
- }
- final AppWindowToken aToken = token.asAppWindowToken();
- if (type == TYPE_APPLICATION_STARTING && aToken != null) {
- aToken.startingWindow = win;
- if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow:" + aToken
- + "startingWindow=" + win);
- }
- boolean imMayMove = true;
- win.mToken.addWindow(win);//7
- if (type == TYPE_INPUT_METHOD) {
- win.mGivenInsetsPending = true;
- 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)) {
- displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- }
- }
- ...
在注释 1 处创建了 WindowState, 它存有窗口的所有的状态信息, 在 WMS 中它代表一个窗口从 WindowState 传入的参数, 可以发现 WindowState 中包含了 WMSSessionWindowToken 父类的 WindowStateLayoutParams 等信息紧接着在注释 2 和 3 处分别判断请求添加窗口的客户端是否已经死亡窗口的 DisplayContent 是否为 null, 如果是则不会再执行下面的代码逻辑注释 4 处调用了 WMP 的 adjustWindowParamsLw 方法, 该方法的实现在 PhoneWindowManager 中, 会根据窗口的 type 对窗口的 LayoutParams 的一些成员变量进行修改注释 5 处调用 WMP 的 prepareAddWindowLw 方法, 用于准备将窗口添加到系统中
注释 6 处将 WindowState 添加到 mWindowMap 中注释 7 处将 WindowState 添加到该 WindowState 对应的 WindowToken 中(实际是保存在 WindowToken 的父类 WindowContainer 中), 这样 WindowToken 就包含了相同组件的 WindowState
addWindow 方法总结
addWindow 方法分了 3 个部分来进行讲解, 主要就是做了下面 4 件事:
对所要添加的窗口进行检查, 如果窗口不满足一些条件, 就不会再执行下面的代码逻辑
WindowToken 相关的处理, 比如有的窗口类型需要提供 WindowToken, 没有提供的话就不会执行下面的代码逻辑, 有的窗口类型则需要由 WMS 隐式创建 WindowToken
WindowState 的创建和相关处理, 将 WindowToken 和 WindowState 相关联
创建和配置 DisplayContent, 完成窗口添加到系统前的准备工作
结语
在本篇文章中我们首先学习了 WMS 的重要成员, 了解这些成员有利于对 WMS 的进一步分析接下来我们又学习了 Window 的添加过程的 WMS 部分, 将 addWindow 方法分为了 3 个部分来进行讲解, 从 addWindow 方法我们得知 WMS 有 3 个重要的类分别是 WindowTokenWindowState 和 DisplayContent, 关于它们会在本系列后续的文章中进行介绍
参考资料
深入理解 Android 卷 III
来源: http://liuwangshu.cn/framework/wms/2-wms-member.html