Window的创建流程

197 阅读10分钟
   每一次的不得意和不痛快都会让你得到成长,不可能都是一帆风顺的。最近的我也很烦躁,工作很久了,有时
候很多从事的都不是我喜欢的,盲目的搬砖,作为底层员工你只能跟着公司的大方向走,但是我也是一个单独
的个体有时候大家也要多位自己想想。

问题

之前说过,要给大家讲讲Window和View的关系,有点忽略了,这里补上,首先抛出几个问题问题:

  • window和view的关系是什么样的?
  • window怎么设置大小呢,什么时候需要设置大小呢?
  • 事件是window和view的传递,我们能跨window透传吗?

真实场景

我们日常开发中很少会遇到直接合Window打招呼的,就算有,Dialog和DialogFragment就可以满足我们的日常开发需求,但有时候我们也是需要自定义的,比如产品就曾经给我提过一个需求:

需求:IM聊天,当消息来的时候,需要在app内部,从顶部往下弹一个悬浮窗下来,如果页面切换,也要保证这个弹窗不能被盖住,依旧虚浮在最上方,而且,这个弹窗还可以实现拖拽效果。

分析:第一想法,就是在Activity的Decorview中添加这个卡片View,但是当页面切换的时候,是会被盖住的,所以直接就被pass了,只能通过单独定义一个window悬浮在APP内。Android的api中,windowmanager就提供了这个能力。

后面我会告诉大家怎么实现这个功能,现在我们先进入正题。

初识WindowManager

  • addView(View view, ViewGroup.LayoutParams params)

    通过这个方法,根据传递来的View和布局参数,创建一个window,这个View你可以理解就是Activity的Decorview,为什么Activity需要Decorview,其实也是为了方便开发者调用,没有什么特别重要的含义。

  • updateViewLayout(View view, ViewGroup.LayoutParams params)

    可以改变window的布局参数,改变大小和位置

  • removeView(View view)

    移除这个window

接下里我们分析这个三个方法,梳理一下流程,看看WindowManager的实现类WindowManagerImpl的操作(其实没有什么特别好的办法,只能自己去梳理一下源代码,看看Android是怎么实现的,慢慢来)

addView分析

// 是一个单例
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        // 交给了WindowManagerGlobal来处理
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

接下来我们看看这个类WindowManagerGlobal,着重注意一下这几个成员,通过这几个成员,我们可以实现一些风骚的操作,比如实现window的时间传递,后面会提及。


    // window内部对应的view
    private final ArrayList<View> mViews = new ArrayList<View>();
    
    // window对应的ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    
    // window对应的布局参数Params
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    
    // 被删除的window对应的View,调用了windowmanager的removeView方法
    private final ArraySet<View> mDyingViews = new ArraySet<View>();

接下来,粘贴出来addview的核心代码

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

        ViewRootImpl root;
        View panelParentView = null;

        // 创建一个ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);
        
        // 一次添加View,ViewRootImpl以及Params
        // 同时他们的顺序是保持一致的
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                // 通过ViewRootImpl将View和布局参数关联
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

接下来,我们看看这个setView方法干了什么,我们核心关注的就是这个Window是怎么创建的,view是怎么绘制的。 以下是核心代码。(其实代码太多了,各种属性,我也不是都很懂,但是我知道我关注的就是这个window怎么创建的,大小怎么控制的,以及view的绘制是怎么操作的,能达到我的目的就是OK的)

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView){
   // 这个attrs里面有我们设置的布局参数,把布局参数拷贝一份
   mWindowAttributes.copyFrom(attrs);
   // 发现了,这个就是view绘制的入口,实现view显示的刷新
   requestLayout();
   // 这个方法就是实现了window的创建
   res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
}

mWindowSession是IWindowSession类型的,我们需要关注这个IWindowSession(www.androidos.net.cn/sourcecode 这个是Android源码观看的在线链接)发现这个类其实是一个AIDL,是一次的IPC过程(关于IPC后续我也会讲解,进程的同学后续也会讲解),那么需要关着这个binder对象的真正实现类,来看。

首先我们看看mWindowSession的初始化,一步步来

mWindowSession = WindowManagerGlobal.getWindowSession();

public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    // 获得IWindowManager的binder代理对象,看下面的方法
                    IWindowManager windowManager = getWindowManagerService();
                    // 调用binder的方法,获取IWindowSession的binder对象
                    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;
        }
    }
    
    
    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                // 发现有时一次IPC的操作,获取IWindowManager的binder代理对象
                // 后面我讲插件化的时候会给大家详细讲解这个ServiceManager的,你可以理解,它就是一                 个注册IBinder的map,实现IPC好多都是走这一套
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    if (sWindowManagerService != null) {
                        ValueAnimator.setDurationScale(
                                sWindowManagerService.getCurrentAnimatorScale());
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }

这里面涉及到了好几次IPC操作,总结一下:

  • 首先,通过IPC获取WindowManagerService对象(WindowManagerService实现了IWindowManager.Stub,那么她就是一个IWindowManager的binder对象)
  • 调用WindowManagerService的openSession方法,会创建一个Session对象,这个Session实现了IWindowSession.Stub,很鲜染,Session也是一个binder。这个Session就是ViewRootImpl的mWindowSession的具体实现binder

然后,ViewRootImpl或得到Session的Binder之后,开始执行addToDisplay方法

@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
         // 最后还是交给了WindowManagerService来处理创建window的过程
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
    }

最后,WindowManagerService代码太多,就不一一分析了,对token和window的类型做了很多的判断,每一个window都有一个windowstate保存其状态,有着window的各种信息。后面会调用WindowManagerPolicy的prepareAddWindowLw准备window,这个就看不到源代码了

其实吧,Activity,dialog,里面的window都是一个虚拟的概念,真正起作用的还是view,以及边界尺寸,有了window这个中间者,方便理解的同时,还可以实现解耦的操作,包括事件分发。应为最后渲染,窗口还是会分配surface,还是通过surface(这一块我不是很懂,还需要学习)完成渲染,而不是窗口本身。这里是我的理解,后续,随着我的深入,我会做更多的讲解。

updateView

@Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        // 同样交给这个单例对象来操作
        mGlobal.updateViewLayout(view, params);
    }
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的布局参数
        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            // 这个方法内部调用了scheduleTraversals,会重新触发测量和布局
            root.setLayoutParams(wparams, false);
        }
    }

removeView

同样,我们需要看mGlobal.removeView即可

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

        synchronized (mLock) {
            // 在mViews集合中,找到要删除的view索引
            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());
            }
        }
        // 默认immediate是false,通过异步来实现删除操作
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                // 把要删除的View放在mDyingViews集合中
                mDyingViews.add(view);
            }
        }
    }

接下来分析具体的删除过程


boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        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());
        }
        // 异步删除,发送一个消息,通过handler来切换
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

不管是同步还是通过handler切换,都会走到doDie方法

void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                // 这个方法,等下会讲一下
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();

                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {                           
                                // 有事一次IPC调用,和动画有关,不是我们关注的
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }
                    // 资源释放
                    mSurface.release();
                }
            }

            mAdded = false;
        }
        // 把mViews,mRoots,mDyingViews中,保留这个这个View相关的数据,清除掉
        // 之前提到的比较重要的几个集合,看来他们的生命周期是绑定的
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

这个方法,就是真正的删除逻辑

void dispatchDetachedFromWindow() {
        mFirstInputStage.onDetachedFromWindow();
        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);

        // 移除回调和屏障消息(关于这个屏障消息,后续分析handler会给大家讲解,目的就是为了加快UI的渲         // 染)
        unscheduleTraversals();
    }

关于,mWindowSession.remove(mWindow)适合addView相似的,最终也会低啊用WindowManagerService的方法removeWindow,最后会调用WindowManagerPolicy的removeWindowLw的方法。

小结

总体来说,流程基本是疏通了,但是到了WindowManagerPolicy这个地方,我就有点找不到代码了(应该是功力不够,后续我又学习到,会持续补充的)这就可能会涉及到更底层,更细节的地方了。

大家需要知道下面几点就足够了

  • window的创建流程,经历了和WMS的IPC操作
  • view,window,ViewRootImpl的关系

接下来,我们回到最开始提的几个问题:

  • window和view的关系是什么样的?

Android的window是一个虚拟的概念,核心还是View的显示,通常理解可以是一个layer,或者一个显示的约束和边界,核心的渲染还是通过surface来实现。一个window就对应着一个view树,我们方便通过window来管理view,管理屏幕上显示的各个view树,他们的事件分发,以及他们的层级等(每个window的优先级是不一样的,优先级高的会在最上方显示),以及通过在window级别的token校验。可能有点抽象,简单理解就是,window是一个班级的老师,管理这个班级,但是班级的平均成绩适合学生有关的(学生的分数/学生人数)。

  • window怎么设置大小呢,什么时候需要设置大小呢?(这个可以不用特别关心,到时候查api即可)

window是可以约束边界的,默认不需要我们来改变,windowmanager的window大小,通过布局参数来改变,当你需要全屏或者其他情况,需要定制你的UI的时候可以控制,以及我想要拦截屏幕的所有事件,也可以通过全屏来拦截。(如果不是全屏,触摸到其他位置,事件根本就不会来到你的winow,怎么拦截)。

  • 事件是window和view的传递,我们能跨window透传吗?

默认情况下,点击事件只会在当前window传递,如果能随便跨window就会出问题了(打开新的activity,被盖住的activity也有了这个事件,这不是bug吗),但是也是可以操作的,我们通过api获取到WindowManager(实际上就是WindowManagerIMpl这个类的实例对象),然后通过反射,获取到mGlobal对象,然后获取mViews集合,就可以获取到另外一个窗口的View根节点,可以理解为DecorView,然后通过这个DecorView重新分发一次事件dispatchTouchEvent(ev)即可。

核心类功能说明:

  • WindowManager:
    • 是一个接口,提供三个方法,实现类是WindowManagerImpl,最后通过WindowManagerGlobal这个单例实现,对window的各种操作。
  • ViewRootImpl
    • 负责连接view和window的桥梁。
    • 负责和WindowManagerService的IPC同学。
    • 负责管理和绘制view视图。
  • WindowManagerService
    • 这个是Window具体创建和管理的地方