WindowManagerService的理解


Android中的窗口概念:屏幕上一块用于绘制各种UI元素并可以响应用户输入的一块矩形区域。从原理上来看,窗口的概念是独自占有一个Surface实例的显示区域。如Dialog、Activity的界面、壁纸、状态栏以及Toast等都是窗口。
上述对窗口的概念性描述,提取出来主要有以下几点信息:

  • 用于绘制UI元素

  • 响应用户输入

  • 独自占有一个surface实例

    • Surface是一块画布,应用通过canvas或者openGL在上面作画
    • 作画后通过SurfaceFlinger将多块Surface的内容按照Z-order进行混合并输出到FrameBuffer,从而将Android的页面显示给用户。

每个窗口都有一块Surface用于显示自己的ui,必然需要一个角色对窗口进行统一管理,这个时候,WMS应运而生。WMS为所有窗口分配Surface,掌管z-order以及位置、尺寸、窗口的进场出场动画,并且还是输入系统的中转站。本文的分析角度:

  • 布局系统:计算管理窗口的位置和层次
  • 动画系统:根据布局系统的计算渲染窗口动画

一个窗口的创建流程大概如下:
1. 客户端通过WindowManagerGlobal获取IWindowSession,通过IWindowSession来向WMS发起添加请求。
2. 初始化WindowManager.LayoutParam,指定响应的type,当type大于System级别的时候,不需要token,WMS会创建一个token,而type小于system级别的窗口,是必须要求有token带过来给WMS的。WMS根据这个type来对窗口的z-order进行排序。
3. 向was申请relayout,所谓重新布局,是根据窗口新的属性去调整其Surface的相关属性,或者重新创建一个Surface。向wms添加一个窗口,仅仅是将它在wms中进行注册。只有经过了relayout后,窗口才拥有了wms为其分配的画布,有了画布,窗口才能进行绘制工作。
4. 之后,窗口的绘制过程如下:
- 通过surface.lock()函数获取可以在surface上作画的canvas
- 使用canvas进行绘图
- 通过unlockCanvasAndPost提交绘制结果

说到底,Window的本质其实就是一块Surface画布.
于是,根据对Surface的操作类型可以将Android的显示系统分为3个层次。
1. UI框架层,负责View空间的布局、绘制、事件分发、响应
2. WMS管理窗口Surface的布局与次序
3. SurfaceFlinger 将WMS维护的窗口Surface按照一定的次序混合后显示到屏幕上。

说了那么多,我们来看看窗口的核心管理WMS的构成。
- WMS也属于系统服务,都是由SystemServer来启动,代码位于SystemServer.java中:

//这里需要加入SystemServer启动WMS的相关代码,最后进入到WMS.Main函数中
//可以看到,SystemServer启动WMS的过程是将WSM的构造函数所需要的相关参数传入main方法
    //由main方法来调用HandlerThread的handler将构造函数丢入looper中进行wms的实例化
    public static WindowManagerService main(final Context context,
            final InputManagerService im,
            final boolean haveInputMethods, final boolean showBootMsgs,
            final boolean onlyCore) {
        final WindowManagerService[] holder = new WindowManagerService[1];
        //那么这里的runWithScissor到底起了什么作用呢?
        DisplayThread.getHandler().runWithScissors(new Runnable() {
            @Override
            public void run() {
                holder[0] = new WindowManagerService(context, im,
                        haveInputMethods, showBootMsgs, onlyCore);
            }
        }, 0);
        return holder[0];
    }
//这个方法位于android.os.Handler.java类,是一个@hide方法
//同时这个方法是一个阻塞的方法,会阻塞目前县城直到返回,这也就
//说,return的时候在wms线程会被此方法阻塞,直到wms完成实例化
//,也可以理解为,main方法return后,holder可能仍然为null,
//但是wms线程中,所有消息都需要等待这个实例化完成
public final boolean runWithScissors(final Runnable r, long timeout) {
        if (r == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout must be non-negative");
        }

        //由于 wms 的 Main()方法调用
        //如果调用此方法的looper和handler的looper相同,则直接运行返回
        if (Looper.myLooper() == mLooper) {
            r.run();
            return true;
        }

        //如果不是则新建一个阻塞的runnable来进入msgqueue里面
        BlockingRunnable br = new BlockingRunnable(r);
        return br.postAndWait(this, timeout);
    }

接下来,看wms的构造函数,所有参数都是SystemServer传入:

 private WindowManagerService(Context context, InputManagerService inputManager,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
        mContext = context;
        mHaveInputMethods = haveInputMethods;
        mAllowBootMessages = showBootMsgs;
        mOnlyCore = onlyCore;
        ...
        //保存了用于接受输入事件的inputManager
        mInputManager = inputManager; // Must be before createDisplayContentLocked.
        ...
        //保存屏幕相关信息
        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
        mDisplays = mDisplayManager.getDisplays();
        for (Display display : mDisplays) {
            createDisplayContentLocked(display);
        }
        ...
        //掌管所有window的动画
        mAnimator = new WindowAnimator(this);

         //初始化WindowManagerPolicy成员
        initPolicy();

        // Add ourself to the Watchdog monitors.
        Watchdog.getInstance().addMonitor(this);

        SurfaceControl.openTransaction();
        try {
            createWatermarkInTransaction();
        } finally {
            SurfaceControl.closeTransaction();
        }

        showEmulatorDisplayOverlayIfNeeded();
    }

至此,WMS的初始化工作已经完成,那么初始化工作有哪些重要的成员变量被初始化呢?

  • mInputManager : 输入系统服务,用于管理每个窗口的输入事件管道(InputManager)以及向通道上派发事件。
  • mChoreographer ,从显示子系统获取VSYNC同步事件,从而来在合适的实际通知渲染动作,避免在渲染的过程中因为发生屏幕重绘而导致画面撕裂。WMS使用这个对象驱动所有的窗口动画,屏幕旋转动画,墙纸动画的渲染。
  • mAnimator,WindowAnimator实例,在Choreographer的驱动下逐个渲染所有动画。
  • mPolicy,WindowManagerPolicy的实现,目前只有PhoneWindowManager一个实现类。mPolicy定义了很多窗口相关的策略,是wms的首席顾问,当WMS要做任何事情的时候,都会向policy求助。例如,过塑wms某一个类型的window 的zOrder是多少,帮助wms矫正不合理的窗口属性,为wms监听屏幕旋转状态。预处理一些系统按键事件,例如(home back键都在这里实现默认行为),所以mPolicy是一个非常重要的成员变量。
  • mDisplayContents ,一个DisplayContent列表,4.2支持基于Wi-Fi Display的多屏幕输出,而一个DisplayContent描述了一块可以绘制窗口的屏幕。每个DisplayContent都用一个整形变量做为其ID。
  • mTokenMap,一个hash map,保存了所有显示令牌(类型为WindowToken),用于窗口管理。一个窗口必须有一个显示令牌,mAppTokens保存了所有的Activity令牌(WindowToken的子类AppWindowToken),mExitingTokens则保存了正在退出过程中的显示令牌,其中mAppTokens列表是有序的,他与AMS中的mHistory的顺序保持一致,反应了系统中activity的顺序。
  • mWindowMap,也是一个HashMap,保存了所有窗口的状态信息,用于窗口管理。状态信息的类型为WindowState。实际上,所有被add过的window的状态都会保存在这个map中,与mTokenMap一样,mWindowMap也有衍生的子集,mPendingRemove保存了退出动画播放完成即将被移除的窗口,mLosingFocus保存了失去输入焦点的窗口。在DisplayContent中,也有一个window列表,这个列表以order为顺序存储了所有window。
  • mSessions,一个List,元素为Session,这个列表保存了所有当前向WMS发起请求的客户端进程唯一的IWindowSession对象,注意,这里的Session也是进程唯一的。
  • mRotation,是一个int变量。保存了当前手机的旋转状态。

接下来,我们从WindowManagerService的addWindow()函数开始讨论WMS的工作流程:


    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);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        boolean reportNewConfig = false;

        //当为某个窗口添加子窗口的时候,attachedWindow用来保存父窗口的实例
        WindowState attachedWindow = null;
        long origId;
        final int type = attrs.type;

        synchronized(mWindowMap) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }

            // 获取窗口要添加的displayContent
            // 在添加窗口时,必须的通过displayId参数指定添加哪个DisplayContent,如果没有指定,session会替Window添加DefaultDisplay
            // 也就是手机屏幕
            final DisplayContent displayContent = getDisplayContentLocked(displayId);
            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 (!displayContent.hasAccess(session.mUid)) {
                Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
                        + "does not have access: " + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            //已经添加了窗口
            if (mWindowMap.containsKey(client.asBinder())) {
                Slog.w(TAG_WM, "Window " + client + " is already added");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

            // 如果是type是子窗口
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {

                //检查添加窗口的父窗口是否为null
                attachedWindow = windowForClientLocked(null, attrs.token, false);
                if (attachedWindow == 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 (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && attachedWindow.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;
                }
            }

            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display.  Aborting.");
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }


            boolean addToken = false;

            //从tokenMap中取出已经注册的token
            WindowToken token = mTokenMap.get(attrs.token);
            AppWindowToken atoken = null;
            //如果看看已经注册的token没有
            if (token == null) {
                //如果是application 级别 activity
                if (type >= FIRST_APPLICATION_WINDOW && type <= 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 (type == 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 (type == 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 (type == 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_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }

                //如果说以上的情况,都new一个token,最后一个参数是false,表明是wms创建的token
                token = new WindowToken(this, attrs.token, -1, false);
                //说明token是临时创建的
                addToken = true;

                //如果说token不为null,则说明已经被注册过了 ,看一下activity级别的讨论,这里要求级别也是application的
                //并且atoken是appWindowToken
            } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                atoken = token.appWindowToken;
                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;
                }
                if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
                    // No need for this guy!
                    if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
                            TAG_WM, "**** NO NEED TO START: " + attrs.getTitle());
                    return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
                }
            } else if (type == 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;
                }
            } else if (type == TYPE_VOICE_INTERACTION) {
                if (token.windowType != TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_WALLPAPER) {
                if (token.windowType != TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_DREAM) {
                if (token.windowType != TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_QS_DIALOG) {
                if (token.windowType != TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (token.appWindowToken != null) {
                Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type);
                // It is not valid to use an app token with other system types; we will
                // instead make a new token for it (as if null had been passed in for the token).
                attrs.token = null;
                token = new WindowToken(this, null, -1, false);
                addToken = true;
            }

            //这个windowState维护了所有窗口的信息
            WindowState win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
            if (win.mDeathRecipient == null) {
                // 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) {
                Slog.w(TAG_WM, "Adding window to Display that has been removed.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            //windowPolicy粉墨登场
            mPolicy.adjustWindowParamsLw(win.mAttrs);
            win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

            res = mPolicy.prepareAddWindowLw(win, attrs);
            if (res != WindowManagerGlobal.ADD_OKAY) {
                return res;
            }

            final boolean openInputChannels = (outInputChannel != null
                    && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
            if  (openInputChannels) {
                win.openInputChannel(outInputChannel);
            }

            // From now on, no exceptions or errors allowed!

            res = WindowManagerGlobal.ADD_OKAY;

            if (excludeWindowTypeFromTapOutTask(type)) {
                displayContent.mTapExcludedWindows.add(win);
            }

            origId = Binder.clearCallingIdentity();

            //刚才被wms临时创建的token被加入到tokenMap中
            if (addToken) {
                mTokenMap.put(attrs.token, token);
            }
            win.attach();

            //然后将WindowState加入到map中
            mWindowMap.put(client.asBinder(), win);
            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);
                }
            }

            if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) {
                token.appWindowToken.startingWindow = win;
                if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + token.appWindowToken
                        + " startingWindow=" + win);
            }

            boolean imMayMove = true;

            if (type == TYPE_INPUT_METHOD) {
                win.mGivenInsetsPending = true;
                mInputMethodWindow = win;
                addInputMethodWindowToListLocked(win);
                imMayMove = false;
            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
                mInputMethodDialogs.add(win);
                addWindowToListInOrderLocked(win, true);
                moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));
                imMayMove = false;
            } else {
                addWindowToListInOrderLocked(win, true);
                if (type == TYPE_WALLPAPER) {
                    mWallpaperControllerLocked.clearLastWallpaperTimeoutTime();
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if (mWallpaperControllerLocked.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;
                }
            }

            // If the window is being added to a task that's docked but non-resizeable,
            // we need to update this new window's scroll position when it's added.
            win.applyScrollIfNeeded();

            // If the window is being added to a stack that's currently adjusted for IME,
            // make sure to apply the same adjust to this new window.
            win.applyAdjustForImeIfNeeded();

            if (type == TYPE_DOCK_DIVIDER) {
                getDefaultDisplayContentLocked().getDockedDividerController().setWindow(win);
            }

            final WindowStateAnimator winAnimator = win.mWinAnimator;
            winAnimator.mEnterAnimationPending = true;
            winAnimator.mEnteringAnimation = true;
            // Check if we need to prepare a transition for replacing window first.
            if (atoken != null && !prepareWindowReplacementTransition(atoken)) {
                // If not, check if need to set up a dummy transition during display freeze
                // so that the unfreeze wait for the apps to draw. This might be needed if
                // the app is relaunching.
                prepareNoneTransitionForRelaunching(atoken);
            }

            if (displayContent.isDefaultDisplay) {
                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
                final Rect taskBounds;
                if (atoken != null && atoken.mTask != null) {
                    taskBounds = mTmpRect;
                    atoken.mTask.getBounds(mTmpRect);
                } else {
                    taskBounds = null;
                }
                if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, mRotation,
                        displayInfo.logicalWidth, displayInfo.logicalHeight, outContentInsets,
                        outStableInsets, outOutsets)) {
                    res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
                }
            } else {
                outContentInsets.setEmpty();
                outStableInsets.setEmpty();
            }

            if (mInTouchMode) {
                res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
            }
            if (win.mAppToken == null || !win.mAppToken.clientHidden) {
                res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
            }

            mInputMonitor.setUpdateInputWindowsNeededLw();

            boolean focusChanged = false;
            if (win.canReceiveKeys()) {
                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                        false /*updateInputWindows*/);
                if (focusChanged) {
                    imMayMove = false;
                }
            }

            if (imMayMove) {
                moveInputMethodWindowsIfNeededLocked(false);
            }

            mLayersController.assignLayersLocked(displayContent.getWindowList());
            // Don't do layout here, the window must call
            // relayout to be displayed, so we'll do it there.

            if (focusChanged) {
                mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
            }
            mInputMonitor.updateInputWindowsLw(false /*force*/);

            if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
                    + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));

            if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
                reportNewConfig = true;
            }
            if (attrs.removeTimeoutMilliseconds > 0) {
                mH.sendMessageDelayed(
                        mH.obtainMessage(H.WINDOW_REMOVE_TIMEOUT, win),
                        attrs.removeTimeoutMilliseconds);
            }
        }

        if (reportNewConfig) {
            sendNewConfiguration();
        }

        Binder.restoreCallingIdentity(origId);

        return res;
    }

接下来,我们来理解WindowToken的重要作用:

WindowToken类位于:com.android.server.wm.WindowToken.java,

/**
 * Container of a set of related windows in the window manager.  Often this
 * is an AppWindowToken, which is the handle for an Activity that it uses
 * to display windows.  For nested windows, there is a WindowToken created for
 * the parent window to manage its children.
 */
//先看类说明,WindowToken是一个位于WindowManager中相互关联的Window的结合。
//对于一个activity来说,WindowToken是一个AppWindowToken的句柄(WindowToken的一个子类),用来显示各种window。对于嵌套的window来说,会创建一个WindowToken来给父window管理儿子。
class WindowToken {
    // The window manager!
    final WindowManagerService service;

    // The actual token.
    final IBinder token;

    // The type of window this token is for, as per WindowManager.LayoutParams.
    final int windowType;

    // Set if this token was explicitly added by a client, so should
    // not be removed when all windows are removed.
    final boolean explicit;

    // For printing.
    String stringName;

    // If this is an AppWindowToken, this is non-null.
    AppWindowToken appWindowToken;

    // All of the windows associated with this token.
    final WindowList windows = new WindowList();

    // Is key dispatching paused for this token?
    boolean paused = false;

    // Should this token's windows be hidden?
    boolean hidden;

    // Temporary for finding which tokens no longer have visible windows.
    boolean hasVisible;

    // Set to true when this token is in a pending transaction where it
    // will be shown.
    boolean waitingToShow;

    // Set to true when this token is in a pending transaction where its
    // windows will be put to the bottom of the list.
    boolean sendingToBottom;

    WindowToken(WindowManagerService _service, IBinder _token, int type, boolean _explicit) {
        service = _service;
        token = _token;
        windowType = type;
        explicit = _explicit;
    }

    void removeAllWindows() {
        for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
            WindowState win = windows.get(winNdx);
            if (DEBUG_WINDOW_MOVEMENT) Slog.w(TAG_WM, "removeAllWindows: removing win=" + win);
            win.mService.removeWindowLocked(win);
        }
        windows.clear();
    }

    void dump(PrintWriter pw, String prefix) {
        pw.print(prefix); pw.print("windows="); pw.println(windows);
        pw.print(prefix); pw.print("windowType="); pw.print(windowType);
                pw.print(" hidden="); pw.print(hidden);
                pw.print(" hasVisible="); pw.println(hasVisible);
        if (waitingToShow || sendingToBottom) {
            pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow);
                    pw.print(" sendingToBottom="); pw.print(sendingToBottom);
        }
    }

    @Override
    public String toString() {
        if (stringName == null) {
            StringBuilder sb = new StringBuilder();
            sb.append("WindowToken{");
            sb.append(Integer.toHexString(System.identityHashCode(this)));
            sb.append(" "); sb.append(token); sb.append('}');
            stringName = sb.toString();
        }
        return stringName;
    }
}
  • WindowToken将同一个应用组件的窗口组织在一起,应用组件可以是Activity,InputMethod,WallPaper以及Dream。在WMS管理窗口的过程中,用WindowToken指代一个应用组件。例如在进行窗口ZOrder的排序中,属于同一个WindowToken的窗口会安排在一起。在其中定义的一些属性会影响到所有属于这个windowToken的窗口。
  • WindowToken具有令牌的作用,这是WMS管理应用组件的一个手段。WindowToken由应用组件或其管理者负责向WMS生命并持有。应用组件在需要新的窗口时,必须提供WindowToken以表明自己的身份,并且窗口的类型必须与所持有的WindowToken类型一致。从前面代码看到,System级别窗口不需要提供Token,会由WMS隐式的创建一个WindowToken。那么是不是所有人都能够添加一个系统窗口呢?可以看到addWindow刚开始mPolicy就对应用进行了permission的检查,也就是客户端持有SYSTEM_ALERT_WINDOW等呼出系统窗口的权限,才能创建系统窗口。

那么,应用组件在创建一个窗口的时候必须指定的WindowToken,到底该如何声明呢?
回到WMS的addWindowToken()函数中,

//可以看到,token其实是一个Binder对象的包装
  @Override
    public void addWindowToken(IBinder token, int type) {
        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                "addWindowToken()")) {
            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
        }

        synchronized(mWindowMap) {
        //去map中找token,以binder对象为键,WindowToken为值
            WindowToken wtoken = mTokenMap.get(token);
            if (wtoken != null) {
                Slog.w(TAG_WM, "Attempted to add existing input method token: " + token);
                return;
            }
            //创建一个windowToken,放入map中
            wtoken = new WindowToken(this, token, type, true);
            mTokenMap.put(token, wtoken);
            if (type == TYPE_WALLPAPER) {
                mWallpaperControllerLocked.addWallpaperToken(wtoken);
            }
        }
    }

从addWindowToken()这个函数可以看到,WindowToken有两层含义。
- 对显示组件(客户端)而言,token是一个任意的binder实例,对客户端而言,token仅仅是一个创建窗口的令牌,没有其他含义。
- 对WMS来说,WindowToken是一个WindowToken的实例,包装了客户端一侧的binder实例,并且以这个token为键,以WindowToken为值,保存了token。mTokenMap是否已经保存了该binder实例和对应的WindowToken,标志着客户端一侧的binder token是否已经在WMS声明。

  • WMS通过token来管理各个窗口,那么最重要的Activity的token是如何管理的呢,首先Activity的token使用方式与WallPaper和InputMethod类似的,但是扩展了更多的内容。对此,WMS创建了一个继承WindowToken的AppWindowToken来对Activity进行描述和管理。既然AppWindowToken是为Activity服务的,那么其声明也自然是在ActivityManagerService中,具体位置在ActivityStack.validateAppTokensLocked()中,代码如下:
 final void validateAppTokensLocked() {
        mValidateAppTokens.clear();
        mValidateAppTokens.ensureCapacity(numActivities());
        final int numTasks = mTaskHistory.size();
        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
            TaskRecord task = mTaskHistory.get(taskNdx);
            final ArrayList activities = task.mActivities;
            if (activities.isEmpty()) {
                continue;
            }
            TaskGroup group = new TaskGroup();
            group.taskId = task.taskId;
            mValidateAppTokens.add(group);
            final int numActivities = activities.size();
            for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
                final ActivityRecord r = activities.get(activityNdx);
                group.tokens.add(r.appToken);
            }
        }
//WMS对appTokens进行更新
        mWindowManager.validateAppTokens(mStackId, mValidateAppTokens);
    }

下面一段代码是AppToken的aidl类,可以看出,实际的binder对象是由这个AIDL文件生成的stub类,分别有一些方法供给WMS进行回调

interface IApplicationToken
{
//窗口完成初次绘制
    void windowsDrawn();
    //窗口可见通知AMS
    void windowsVisible();
    //窗口不可见通知AMS
    void windowsGone();
    //窗口没能按时完成输入事件的处理
    boolean keyDispatchingTimedOut(String reason);
    //从AMS处获取界定ANR的事件
    long getKeyDispatchingTimeout();
}

AMS通过ActivityRecord表示一个Activity,而ActivityRecord的appToken在其构造函数时被创建,类型为IApplicationToken.Stub,就是上述AIDL的stub类,所以每个Activity有自己的appToken,而WMS接受AMS对Application的Binder对象appToken粘结了AMS的ActivityRecord与WMS的AppWindowToken,只要给定一个ActivityRecord,都可以通过appToken在WMS中找到对应的AppWindowToken,从而使得AMS拥有了操纵Activity窗口绘制的能力。例如,当AMS认为一个Activity需要被隐藏的时候,以Activity对应的ActivityRecord所拥有的appToken为参数调用WMS的setAppVisibility()函数,此函数通过appToken找到appdWindowToken(通过mAppWindowMap),然后将这个token的窗口隐藏。

  • 理解WindowState:

从WindowManagerService.addWindow()函数的实现看到,当WMS添加一个窗口时,WMS会为其创建一个WindowState。WindowState表示一个窗口的所有实行,所以他是WMS中事实上的窗口。
类似于WindowToken,WindowState在显示组件一侧也有对应的类型:IWindow.Stub 。这个借口提供了窗口管理相关的通知回调,如尺寸的变化、焦点的变化等。WindowState被保存到mWindowMap中,键值为IWindow的Bp端。mWindowMap是整个系统所有窗口的一个全集。

你可能感兴趣的:(Android)