Window源码解析(二):Window的添加机制

博文出处:Window源码解析(二):Window的添加机制,欢迎大家关注我的博客,谢谢!

注:本文解析的源码基于 API 25,部分内容来自于《Android开发艺术探索》。

第一篇:《Window源码解析(一):与DecorView的那些事》

Header

在上一篇中,我们讲了 Window 和 DecorView 的那些事,如果没有看过的同学请点击这里:《Window源码解析(一):与DecorView的那些事》

而今天就要来详细了解 Window 的添加机制了,到底在 WindowManager.addView 中做了什么事情?我们一起来看看吧!!

Window的添加机制

上面我们看到了在 makeVisible() 中调用了 wm.addView(mDecor, getWindow().getAttributes()) 将 DecorView 视图添加到 Window 上。

那么调用这句代码之后究竟发生了什么呢,这就需要我们一步一步慢慢去揭开了。

WindowManager

WindowManager 是一个接口,继承了 ViewManager 。

    public interface ViewManager
    {
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }

可以看到,ViewManager 中定义的方法非常熟悉,也是平时我们经常使用的,就是对 View 的增删改。

对 WindowManager 具体的实现就是 WindowManagerImpl 这个类了。在后面我们会接触到它的。

那么,我们就开始吧。

WindowManagerImpl

addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params)

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        // 检查是否需要使用默认的 token ,token 就是一个 binder 对象
        // 如果没有父 window ,那么我们需要使用默认的 token
        applyDefaultToken(params);
        // 调用 WindowManagerGlobal 来实现添加 view
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

发现在 WindowManagerImpl 也没有直接实现 View 的添加,而是转交给了 WindowManagerGlobal 类来做这件事。其实除了 addView 之外,updateViewLayoutremoveView 也都是通过 WindowManagerGlobal 来实现的,这是桥接模式的体现。

那么我们继续跟下去。

WindowManagerGlobal

addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow)

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 检查参数有无错误,如果是子 window 的话要调整一些参数
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
            // 创建新的 viewrootimpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            // 保存当前界面这些参数
            // mViews 存储所有 window 所对应的 view
            mViews.add(view);
            // mRoots 存储所有 window 所对应的 ViewRootImpl
            mRoots.add(root);
            // mParams 存储所有 window 所对应的布局参数
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            // 调用 setview 来开始 view 的测量 布局 绘制流程,完成 window 的添加
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) 中,我们捋一捋它干了什么事:

  1. 检查了参数,如果是子 Window 的话,还要调整参数;
  2. 创建 ViewRootImpl ,然后将当前界面的参数保存起来;
  3. 调用 ViewRootImpl 的 setView 来更新界面并完成 Window 的添加;

可以看出,Window 的添加还需要我们到 ViewRootImpl.setView 中去看,同时也即将开启 View 三大工作流程。

ViewRootImpl

setView(View view, WindowManager.LayoutParams attrs, View panelParentView)

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

            // 开始了 view 的三大工作流程
            ...

            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                // 利用 mWindowSession 来添加 window ,是一个 IPC 的过程
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
                mAdded = false;
                mView = null;
                mAttachInfo.mRootView = null;
                mInputChannel = null;
                mFallbackEventHandler.setView(null);
                unscheduleTraversals();
                setAccessibilityFocus(null, null);
                throw new RuntimeException("Adding window failed", e);
            } finally {
                if (restore) {
                    attrs.restore();
                }
            }

            ...

        // 检查 IPC 的结果,若不是 ADD_OKAY ,就说明添加 window 失败
        if (res < WindowManagerGlobal.ADD_OKAY) {
                mAttachInfo.mRootView = null;
                mAdded = false;
                mFallbackEventHandler.setView(null);
                unscheduleTraversals();
                setAccessibilityFocus(null, null);
                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);
            }

            ...

    }

在这里,View 也开始了测量、布局、绘制的三大流程。

之后,利用 mWindowSession 来添加 window ,mWindowSession 的类型是 IWindowSession ,它是一个 Binder 对象,其真正的实现类是 Session 。所以这是一个 IPC 的过程。这步具体的实现我们下面再看。

在添加完成后,根据返回值 res 来判断添加 window 是否成功。若不是 WindowManagerGlobal.ADD_OKAY 则说明添加失败了,抛出对应的异常。

Session

addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel)

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

在 Session 中,发现添加 Window 的操作交给了 mService ,而 mService 其实就是 WindowManagerService 。终于来到了最终 boss 这里了,那我们直击要害吧!

WindowManagerService

addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel)

    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];
        // 校验 window 的权限,如果不是 ADD_OKAY 就不通过
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
            
        // 初步校验一些参数,不通过就会返回错误的 res 值 
        // 比如检查子窗口,就要求父窗口必须已经存在等
        ...

        boolean addToken = false;
        // 拿到 layoutparams.token ,进行校验
        WindowToken token = mTokenMap.get(attrs.token);
        AppWindowToken atoken = null;
        boolean addToastWindowRequiresToken = false;

        // 校验 token 有效性, 如果 token 为空或不正确的话,那么直接返回 ADD_BAD_APP_TOKEN 等异常
        if (token == null) {
            ...
        }else if (...) {
            ...
        } 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);

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

        ...

        if (type == TYPE_INPUT_METHOD) {
            win.mGivenInsetsPending = true;
            mInputMethodWindow = win;
            addInputMethodWindowToListLocked(win);
            imMayMove = false;
        } else if (type == TYPE_INPUT_METHOD_DIALOG) {
            mInputMethodDialogs.add(win);
            // 将新的 WindowState 按显示次序插入到当前 DisplayContent 的 mWindows 列表中
            addWindowToListInOrderLocked(win, true);
            moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));
            imMayMove = false;
        } else {
            // 将新的 WindowState 按显示次序插入到当前 DisplayContent 的 mWindows 列表中
            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;
            }
        }

        // 根据窗口的排序结果,为 DisplayContent 的所有窗口分配最终的显示次序
        mLayersController.assignLayersLocked(displayContent.getWindowList());

        ...

        // 返回添加窗口的结果
        return res;
    }

在 WindowManagerService 中做的事情有很多,一开始利用 mPolicy.checkAddPermission 检查了权限,这里面可大有文章,利用 type = WindowManager.LayoutParams.TYPE_TOAST 来跳过权限显示悬浮窗的故事就来自于这里。想详细了解的同学请看《Android 悬浮窗权限各机型各系统适配大全》

然后就是校验了一些参数,比如 token 。token 是用来表示窗口的一个令牌,其实是一个 Binder 对象。只有符合条件的 token 才能被 WindowManagerService 通过并添加到应用上。

再然后就是创建了一个 WindowState 对象,利用这个对象按照显示次序插入 mWindows 列表中,最后就是依据排序来确定窗口的最终显示次序。并返回了 Window 添加的结果 res 。

到这,整个添加 Window 的过程就结束了。

Footer

Window 添加其实就是一个 IPC 的过程,而更新和删除 Window 也是如此,基本上步骤都是相似的。

接下来就顺便把 Window 更新和删除的流程都梳理一遍吧。

静静等待此系列第三篇出炉!

References

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容