前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Window, WindowManager和WindowManagerService

Window, WindowManager和WindowManagerService

作者头像
提莫队长
发布2020-06-03 10:17:39
7480
发布2020-06-03 10:17:39
举报
文章被收录于专栏:刘晓杰刘晓杰

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.状态图

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.Window是什么
  • 2.WindowManager
  • 3.WindowManagerGlobal
  • 4.状态图
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档