Window 和 WindowManager 解析

2,189 阅读6分钟
原文链接: ztelur.github.io

 这几天阅读了《Android开发艺术探索》的关于Window和WindowManager的章节,特此写一片博文来整理和总结一下学到的知识.
 说到Window,大家都会想到所有的视图,包括Activity,Dialog,Toast,它们实际上都是附加在Window上的,Window是这些视图的管理者.今天我们就来具体将一下这些视图是如何附加在Window上的,Window有是如何管理这些视图的.

Window的属性和类别

 当我们通过WindowManager添加Window时,可以通过WindowManger.LayoutParams来确定Window的属性和类别.其中Flags参数标示Window的属性,我们列出几个比较常见的属性:

  • FLAG_NOT_FOCUSABLE 这个参数表示Window不需要获取焦点,也不需要接收任何输入事件

  • FLAG_NOT_TOUCH_MODAL 这个参数表示当前Window区域之外的点击事件传递给底层Window,区域之内的点击事件自己处理,一般默认开启

  • FLAG_SHOW_WHEN_LOCKED 这个属性可以让Window显示在锁屏界面上
     Window不仅有属性,还有类型.Type参数表示Window的类型,分别为应用Window(activity对应的),子window(dialog对应的),和系统Window(Toast和系统通知栏).Window是分层的,每个window都有z-ordered,层级大的window会覆盖层级小的window,其大小关系为系统window>子window>应用window.所以系统window总会显示在最上边,但是使用系统window是需要声明相应的权限的.这一点需要注意.

    WindowManager

     我们先来看一下WindowManager的接口,对其接口函数的了解有助于我们更好的理解Window的类别和属性.
     WindowManger实现了ViewManager这个接口,所提供的主要函数只有三个:

    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
    

     而且通过阅读源码,我们会发现所有的操作都是交由WindowManagerGloalal来进行.之后的小节我会依次介绍.这一节先讲一下它的比较重要的成员变量.

    // 存储所有window所对应的view
        private final ArrayList mViews = new ArrayList();
        // 存在window所对应的viewRootImpl
        private final ArrayList mRoots = new ArrayList();
        // 存储了所有window对应的布局参数
        private final ArrayList mParams =
                new ArrayList();
        // 存储了那些正在被删除的view对象,调用了removeVIew,但是没有完成的
        private final ArraySet mDyingViews = new ArraySet();

    Window的添加过程

     这是WindowManagerGlobal的对应接口

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

     创建ViewRootImpl,并将View添加到相应的列表中

    // 创建ViewRootImpl,然后将下述对象添加到列表中
    root = new ViewRootImpl(view.getContext(), display);
           view.setLayoutParams(wparams);//设置Params
           mViews.add(view);//window列表添加
           mRoots.add(root);//ViewRootImpl列表添加
           mParams.add(wparams);//布局参数列表添加

     通过ViewRootImpl来更新界面完成window的添加过程

    // 添加啦!!!!!!!!这是通过ViewRootImpl的setView来完成
    root.setView(view, wparams, panelParentView);
    

     在ViewRootImpl的setView函数中,会调用requestLayout来完成异步刷新,然后在requestLayout
    中调用scheduleTraversals来进行view绘制.

    @Override
       	public void requestLayout() {
           if (!mHandlingLayoutInLayoutRequest) {
               checkThread();
               mLayoutRequested = true;
               scheduleTraversals(); // 实际View绘制的入口
           }
       }

     最后通过WindowSession来完成Window的添加过程,它是一个Binder对象,通过IPC调用来添加window.
     所以,Window的添加请求就交给WindowManagerService去处理,在其内部为每个应用保留一个单独的Session.

    Window的删除过程

    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的index
            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);
        }
    }

     removeView先通过findViewLocked来查找待删除的View的索引,然后用removeViewLocked来做进一步删除.

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index); //获得当前的view的viewRootImpl
        View view = root.getView();
    
        if (view != null) { //先让imm下降
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate); //die方法只是发送一个请求删除的消息之后就就返回
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);//加入dyingView
            }
        }
    }

     在WindowManager中提供了两种删除接口removeVIew()和removeViewImmediate(),它们分别表示异步和同步删除.而异步操作中会调用die函数,来发送一个MSG_DIE消息来异步删除,ViewRootImpl的Handler会调用doDie(),而如果是同步删除,那么就直接调用doDie(),然后在removeView函数中把View添加到mDyingViews中.

    Window的更新

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
          	.....
    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);//这是主要的方法
           }
       }

     在setLayoutParams中会调用scheduleTraversals来重新绘制.

    Window的创建过程

     不同类型的Window的创建过程不同,这里我只来讲一下Activity的Window的创建过程.在Window的启动过程中,会调用attach()函数来为其关联运行过程中所依赖的一系列上下文环境变量.

    mWindow = PolicyManager.makeNewWindow(this);
    mWindow.setCallback(this);

     Window对象是通过PolicyManager的makeNewWindow方法实现的,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的对应方法.而我们去追寻Window的具体实现类,会发现它就是PhoneWindow,而Activity中最常用的setContentView方法的具体操作都是在PhoneWindow的相应方法中实现的.

     如果没有DecorView,那么就创建它.DecorView是一个FrameLayout,是Activity中的顶级View,一般包括标题栏和内容栏,而且内容栏的id为android.R.id.content,而DecorView的创建过程由installDecor完成,内部会通过generateDecor方法来直接创建DecorView

    if (mContentParent == null) { //如何没有DecorView,那么就新建一个
               installDecor();
           } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
               mContentParent.removeAllViews();
           }
     	if (mDecor == null) {
               mDecor = generateDecor(); //直接new出一个DecorView返回
               ......
           }
           if (mContentParent == null) {
               //[window] 这一步也是很重要的.
               mContentParent = generateLayout(mDecor); 
               .......
               }
           .......

     而在generateLayout中

    //根据不同的style生成不同的decorview啊
           View in = mLayoutInflater.inflate(layoutResource, null);
           // 加入到deco中,所以应该是其第一个child
           decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
           mContentRoot = (ViewGroup) in; 
           //给DecorView的第一个child是mContentView
           // 这是获得所谓的content
           ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    

     将View添加到DecorView的mContentParent中,这步只需要一条语句就可,具体内部细节就不说啦.

    //第二步,将layout添加到mContentParent
           mLayoutInflater.inflate(layoutResID, mContentParent);
    

     然后就是回调Acitivity的onContentChanged方法通知Activity视图已经改变了.

    final Callback cb = getCallback();
           if (cb != null && !isDestroyed()) {
               cb.onContentChanged();
           }

     经历了上述三个步骤,DecorView已经创建并初始化完毕,并且Activity的布局文件已经成功添加到DecorView的mContentParent中,但是DecorView并没有添加到WindowManager中去,也无法接收外界的输入,只有到Acitivity的makeVisible()被调用时,DecorView才真正完成了添加和显示过程.

    //DecorView正式添加并显示
    void makeVisible() {
       if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

    总结

     这篇博文主要是读书笔记式的总结,本来想写一些自己的东西,但是研究的太浅,并且语言组织上还是有不足,以后还需要注意和继续努力啦.