Window内部机制

前言

Window表示一个窗口, Android中所有视图都是通过Window来呈现的, 例如Activity, Dialog, Toast, PopupWindow等等, 所以说Window是View的直接管理者.

Window的应用

WindowManager是外界访问Window的入口, 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);
}

WindowManager就是通过这三个方法实现View的添加,更新和删除. 下面一个简单的demo通过WindowManger添加一个Window.

        Button button = new Button(this);
        button.setText("button");
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
        layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        layoutParams.gravity = Gravity.LEFT|Gravity.TOP;
        layoutParams.x = 100;
        layoutParams.y = 100;
        WindowManager windowManager = getWindowManager();
        windowManager.addView(button, layoutParams);

该Demo可以将一个Button添加到屏幕左上角坐标为(100, 100)的位置.

  1. type属性
    type表示Window的类型, 分应用Window, 子Window, 系统Window.
  • 应用Window对应一个Activity.
  • 子Window不能单独存在, 需要附属在特定的父Window中, 例如Dialog.
  • 系统Window需要声明SYSTEM_ALERT_WINDOW权限才能创建, 例如Toast.
    注意: Window是分层的, 层级大的会覆盖在层级小的Window上面, 应用Window的层级范围是1~99, 子Window的层级范围是1000~1999, 系统Window的层级范围是2000~2999.如果想要Window显示在所有Window最顶层, 只需要将type设置为较大的层级即可.
  1. flags属性
    flags表示Window的属性, 常用的有以下几种:
  • FLAG_NOT_FOCUSABLE: 表示Window不能获取焦点和输入事件, 最终事件会传递到下层具有焦点的Window.此标记会开启FLAG_NOT_TOUCH_MODAL属性.
  • FLAG_NOT_TOUCH_MODAL:设置了该属性, 即使Window获取焦点时(没有设置FLAG_NOT_FOCUSABLE), Window以外的事件会传递到下层Window, Window以内的事件自己处理. 如果不设置该属性, 该Window外部和内部的事件都会自己处理.
  • FLAG_NOT_TOUCHABLE: 表示Window不接受触摸事件.

Window内部机制

在实际使用中无法直接访问Window, 对Window的访问必须通过WindowManager, 而WindowManager实现的三个方法addView, updateViewLayout,removeView都是针对View的, 所以View才是Window存在的实体.
WindowManager是一个接口, 它的具体实现是WindowManagerImpl, 在WindowManagerImpl中对Window的三大操作如下:

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }
    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

WindowManagerImpl并没有直接实现Window的三大操作, 而是将所有操作都委托给了WindowManagerGlobal. WindowManagerImpl的全局变量中通过单例模式实例化WindowManagerGlobal, 说明一个进程只有一个WindowManagerGlobal对象.
下面看WindowManagerGlobal中的三个具体实现.

Window的添加过程
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        //检查参数的合法性
        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;
        //如果是子Window, 需要调整布局参数
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            //不是子Window, 检查是否设置硬件加速
            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) {
            //设置系统参数改变的回调
            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);
            //如果该View已经被添加到WindowManager, 如果该View正在被删除, 则立即删除, 否则抛异常, 提示该View已经被添加
            if (index >= 0) {
                //mDyingViews列表保存正在被删除的View
                if (mDyingViews.contains(view)) {
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
            }

            // 如果是子Window, 找出它所附属的Window
            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, 保存该Window对应的View, ViewRootImpl, 以及布局参数
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);//mViews保存要操作的View
            mRoots.add(root);//mRoots保存View对应的ViewRootImpl
            mParams.add(wparams);//mParams保存View的布局参数

            //通过ViewRootImpl完成View的添加
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

添加过程大致分以下几步:

  1. 检查参数是否合法, 如果是子Window需要调整布局参数.
  2. 创建ViewRootImpl, 并将该Window对应的View, ViewRootImpl, 布局参数做保存.
  3. 通过ViewRootImpl完成View的添加.

ViewRootImpl实现了View和WindowManager之间的通信协议, 是
WindowManagerGlobal操作View的具体实现.
下面看ViewRootImpl的setView方法.

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ......
                //添加View之前先调用layout布局, 以确保接受任何系统事件之前重新布局
                requestLayout();//View的绘制流程
                
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                   ......
                } finally {
                    ......
                }
                ......
                       }
    }

setView方法中通过requestLayout完成View的异步刷新, requestLayout最终调用performTraversals完成View的measure, layout, draw过程.

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ......
        }
    }
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            ......
            performTraversals();
            ......
        }
    }
    private void performTraversals() {
         ......  
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        performLayout(lp, mWidth, mHeight);
        ......
        performDraw();
        ......
    }
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
       ......
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        ......
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ......
    }
    private void performDraw() {
        ......
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        ......
    }

然后通过mWindowSession.addToDisplay完成Window的添加, mWindowSession是IWindowSession, 是一个Binder对象, 最终实现类是Session, 因此Window的添加是一个IPC过程.
下面看Session的addToDisplay方法.

    @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);
    }

mService是WindowManagerService, 由此可见, Window的添加最终交给WindowManagerService去处理, 在WindowManagerService内部会为每个应用保留一个单独的Session. 在WindowManagerService中会创建一个WindowState对象用来表示当前添加的窗口, WindowManagerService负责管理这里些WindowState对象.
至此, Window的添加过程就结束了.

Window的更新过程

Window的更新也是通过WindowManagerGlobal实现的, 下面看WindowManagerGlobal的updateViewLayout方法.

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }

更新过程比较简单, 首先替换掉view的LayoutParams, 然后通过findViewLocked找到该View的索引, 更新mParams列表中对应的布局参数, 最后通过ViewRootImpl的setLayoutParams实现Window的更新.

    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
            ......
            scheduleTraversals();
        }
    }

由上可知, scheduleTraversals方法最终调用performTraversals实现View的measure, layout, draw过程完成重新布局. 除了View本身的重绘以外, performTraversals方法中还会调用relayoutWindow, relayoutWindow中通过WindowSession的relayout方法更新Window, 这个过程最终又交给WindowManagerService的relayoutWindow实现.

Window的删除过程

同样, Window的删除也是通过WindowManagerGlobal.看下WindowManagerGlobal的removeView方法.

    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
    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);
            }
        }
    }

首先通过findViewLocked找到要删除View的索引, 在removeViewLocked方法中找到该View对应的ViewRootImpl, 然后通过ViewRootImpl的die方法完成Window的删除.下面看下ViewRootImpl的die方法.

    boolean die(boolean immediate) {
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
    final class ViewRootHandler extends Handler {
        ......
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            ......
            case MSG_DIE:
                doDie();
                break;
            }
        }
    }

immediate表示同步删除, WindowManager提供了两种删除方法, removeView和removeViewImmediate, 前者为异步删除, 后者为同步删除.
上面WindowManagerImpl的removeView方法调用了WindowManagerGlobal的removeView(view, false), immediate为false, 因此WindowManager的删除属于异步操作.
那么同步删除和异步删除有什么区别呢,
通过die这段代码可知, 如果是同步删除(immediate为true), 直接调用doDie()方法, 如果是异步删除(immediate为false), 通过Handler发送一个MSG_DIE的消息后立即返回true了, 然后将该View加入到mDyingViews中, 此时该View并没有被删除, 只有当消息队列循环处理到该消息时才执行doDie方法.
下面看下ViewRootImpl的doDie方法.

    void doDie() {
        ......
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            //setView时mAdded置为true, doDie之后置为false
            if (mAdded) {
                dispatchDetachedFromWindow();
            }
            ......
            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

首先调用dispatchDetachedFromWindow.

    void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }
        //移除各种监听
        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();

        destroyHardwareRenderer();

        setAccessibilityFocus(null, null);
        //清空数据
        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;

        mSurface.release();

        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }

        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }

        mDisplayManager.unregisterDisplayListener(mDisplayListener);

        unscheduleTraversals();
    }

该方法主要内容:

  1. 调用View的dispatchDetachedFromWindow方法, 内部又调用了View的onDetachedFromWindow方法, 因此, 当通过WindowManager移除View时会调用View的onDetachedFromWindow方法.
  2. 移除各种监听, 清空数据.
  3. 通过WindowSession的remove删除Window, 该方法最终调用了WindowManagerService的removeWindow, 及Window的删除由ViewRootImpl交给了WindowManagerService.

然后通过WindowManagerGlobal的doRemoveView更新数据.

    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();
        }
    }

该方法是在Window删除之后更新参数信息, 包括mRoots, mParams,mViews,mDyingViews.

总结

综上, Window的三大操作都是由WindowManager发起, 交给WindowManagerGlobal处理, 然后通过ViewRootImpl实现View与WindowManager的通信. 大致流程如图所示:

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

推荐阅读更多精彩内容