Window, WindowManager和WindowManagerService

2020-06-03 10:17:39 浏览数 (1)

1.Window是什么

Window在Android开发中是一个窗口的概念,它是一个抽象类,具体的实现类是PhoneWindow,在PhoneWindow中有一个顶级View—DecorView,继承自FrameLayout,我们可以通过getDecorView()获得它,当我们调用Activity的setContentView时,其实最终会调用Window的setContentView,当我们调用Activity的findViewById时,其实最终调用的是Window的findViewById,这也间接的说明了Window是View的直接管理者。但是Window并不是真实存在的,它更多的表示一种抽象的功能集合,View才是Android中的视图呈现形式,绘制到屏幕上的是View不是Window,但是View不能单独存在,它必需依附在Window这个抽象的概念上面,Android中需要依赖Window提供视图的有Activity,Dialog,Toast,PopupWindow,StatusBarWindow(系统状态栏),输入法窗口等,因此Activity,Dialog等视图都对应着一个Window。

2.WindowManager

WindowManager 本身是一个 interface ,先看一下它的父类

代码语言:javascript复制
public interface ViewManager {
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

很明显,有这三大操作.但是 WindowManager 本身不实现这些功能,它们在 WindowManagerImpl 实现 在看 WindowManagerImpl 之前,先看一下 WindowManager 的内部类 LayoutParams

第一个 SoftInputModeFlags
代码语言:javascript复制
                SOFT_INPUT_STATE_UNSPECIFIED,
                SOFT_INPUT_STATE_UNCHANGED,
                SOFT_INPUT_STATE_HIDDEN,
                SOFT_INPUT_STATE_ALWAYS_HIDDEN,
                SOFT_INPUT_STATE_VISIBLE,
                SOFT_INPUT_STATE_ALWAYS_VISIBLE,
                SOFT_INPUT_ADJUST_UNSPECIFIED,
                SOFT_INPUT_ADJUST_RESIZE,
                SOFT_INPUT_ADJUST_PAN,
                SOFT_INPUT_ADJUST_NOTHING,
                SOFT_INPUT_IS_FORWARD_NAVIGATION,

是不是有几个很眼熟,就是控制当然页面软键盘的行为的

第二个 type

这个分类的注视有很多,不过大致把type分为3类

  • 应用程序窗口:type值范围是1~99,Activity就是一个典型的应用程序窗口,type值是TYPE_BASE_APPLICATION,WindowManager的LayoutParams默认type值是TYPE_APPLICATION。
  • 子窗口:type值范围是1000~1999,PupupWindow就是一个典型的子窗口,type值是TYPE_APPLICATION_PANEL,子窗口不能独立存在,必须依附于父窗口
  • 系统窗口:type值范围是2000~2999,系统窗口的类型很多,上面并没有全部列举出来,系统状态栏就是一个典型的系统窗口,type值是TYPE_STATUS_BAR,与应用程序窗口不同的是,系统窗口的创建是需要声明权限的。
第三个 flags

这个分类也很多,不过常用的就是 FLAG_KEEP_SCREEN_ON

剩余还有好几个,不过基本不怎么用,就不解释了 好了,接下来看 WindowManagerImpl 的三个方法的具体实现 (WindowManagerGlobal)

3.WindowManagerGlobal

先介绍几个重要的数据结构

代码语言:javascript复制
    private static IWindowManager sWindowManagerService;---后面讲
    private static IWindowSession sWindowSession;---后面讲
    private final ArrayList<View> mViews = new ArrayList<View>();----所有的view
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();---实现了View与WindowManager之间所需要的协议,作为WindowManagerGlobal中大部分的内部实现
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();----所有的view对应的布局参数
    private final ArraySet<View> mDyingViews = new ArraySet<View>();---需要删除view就放到队列里,等到真正需要删除的时候再删除
(1)addView
代码语言:javascript复制
            int index = findViewLocked(view, false);
            if (index >= 0) {
                // 已经存在,并且在 mDyingViews 里面,就让它真正消失
                // 已经存在,但是不在 mDyingViews 里面,提示重复添加
                if (mDyingViews.contains(view)) {
                    //里面会把它从 mDyingViews 中移除
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View "   view
                              " has already been added to the window manager.");
                }
            }
            ......

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            try {
                // 在 ViewRootImpl 里面
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }

ViewRootImpl.addView

代码语言:javascript复制
                try {
                    .....
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    .....
                } finally {
                    .....
                }
                if (res < WindowManagerGlobal.ADD_OKAY) {
                    ......
                    switch (res) {
                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token "   attrs.token
                                      " is not valid; is your activity running?");
                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token "   attrs.token
                                      " is not for an application");
                        case WindowManagerGlobal.ADD_APP_EXITING:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- app for token "   attrs.token
                                      " is exiting");
                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- window "   mWindow
                                      " has already been added");
                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                            // Silently ignore -- we would have just removed it
                            // right away, anyway.
                            return;
                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                      mWindow   " -- another window of type "
                                      mWindowAttributes.type   " already exists");
                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                      mWindow   " -- permission denied for window type "
                                      mWindowAttributes.type);
                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                      mWindow   " -- the specified display can not be found");
                        case WindowManagerGlobal.ADD_INVALID_TYPE:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                      mWindow   " -- the specified window type "
                                      mWindowAttributes.type   " is not valid");
                    }
                    throw new RuntimeException(
                            "Unable to add window -- unknown error code "   res);
                }

最重要的就是这个. mWindowSession.addToDisplay 如果失败会返回一大堆Exception.如果 WindowManager.addView 失败,大多都会在这里看到exception mWindowSession 在创建的时候就会被初始化.首先看一下 WindowManagerGlobal.getWindowSession()

代码语言:javascript复制
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

其实 sWindowManagerService 和 sWindowSession 都是AIDL.他们说的实现分别在 WindowManagerService 和 Session 那么 mWindowSession.addToDisplay 就是 Session.addToDisplay.最终还是转到 WindowManagerService.addWindow (其实看代码就知道 Session 类似于 WindowManagerService 的代理)

代码语言:javascript复制
    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
            .....
        synchronized(mWindowMap) {
            // 先检查 DisplayContent 有没有问题
            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
            .....

            // 检查 window 类型
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                parentWindow = windowForClientLocked(null, attrs.token, false);
                .....
            }

            // 检查 token
            .....
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            .....
            if (token == null) {
                .....
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow);
            } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                atoken = token.asAppWindowToken();
                .....
            } 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;
                }
            } 
            // 这里省略一大堆的 token 的判断

            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
            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;
            }

            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! 从这里开始没有任何问题 ADD_OKAY

            res = WindowManagerGlobal.ADD_OKAY;
            ......
        }
        return res;
    }
(2)updateViewLayout

这两个相对比较简单,就是mParams对应的值换一下

(3)removeView
代码语言:javascript复制
    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

很明显,remove的时候并没有马上删除.那什么时候真正删除view呢?

代码语言:javascript复制
    void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
        }
        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }
    }

在 doRemoveView 里面,什么时候调用呢?在 ViewRootImpl.doDie 里面.一共三种情况

  • 发送 MSG_DIE 消息
  • 就是上面的 removeViewLocked 里面.稍微看一下 root.die 逻辑就知道.如果 immediate=true 就立马删除,此时 deferred=false.如果 immediate=false,会发送 MSG_DIE 消息,然后添加到 mDyingViews
  • 就是最开始分析 addView 之前检测 mDyingViews 是否包含该view,包含了就删除

4.状态图

0 人点赞