Window和WindowManager浅析

介绍

Window表示一个窗口的概念,它是一个抽象类,其唯一的实现类是PhoneWindow。Android中所有视图的显示都需要通过Window来呈现,可以说只要有视图的地方就有Window,比如Activity,Dialog它们的视图也是需要附加在Window上显示的,所以Window可以说是View的直接管理者。

WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Button button = new Button(this);
button.setText("我是Button");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.FIRST_SUB_WINDOW, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT
);
windowManager.addView(button, layoutParams);

以上就是用WindowManager添加一个Window的过程,addView方法有两个参数,第一个是要添加的View,第二个是WindowManager.LayoutParams对象,LayoutParams有一个type属性,它代表要添加的Window的类型,分为三类。

Window的分类

  • 应用Window

    Activity的窗口类型就是应用Window

  • 子Window

    子Window不能单独存在,需要依附于父Window上,如Dialog、PopWindow等

  • 系统Window

    系统Window是系统层级的Window,如状态栏、Toast等

Window是分层显示的,每个Window都有其对应的z-ordered,层级大的会覆盖在层级小的上面。其中应用Window的层级范围为1-99,子Window层级范围为1000-1999,系统Window层级范围为2000-2999,该层级范围对应着WindowManager.LayoutParams的type属性。上面添加Window的type为FIRST_SUB_WINDOW,它是子Window的初始取值,所以我们添加的是一个子Window。

Window的内部机制

我们在使用WindowManager添加Window时,其实是添加了一个View。Window作为一个抽象概念,它并不是实际存在的,而是以View的形式存在的,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系。

WindowManager接口继承了ViewManager接口,在ViewManager中分别定义了添加,更新和删除Window的方法

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

分别来看Window的添加,更新和删除过程

Window的添加

WindowManager的实现类是WindowManagerImpl,addView方法

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

这里没有具体实现,转而调用mGlobal的方法,mGlobal为WindowManagerGlobal对象

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;
    //...

    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
                //...
        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);
                }
            }
        }

        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}
  • 首先做View等的非空判断以及LayoutParams的类型判断,然后会查找当前添加的View是否已经存在,如果已经存在并且它在等待移除队列中的话,直接将其移除,否则抛出View已经添加过的异常。判断添加的Window类型为子Window的话,还要找到它所附属的父Window。
  • 初始化ViewRootImpl对象,并将LayoutParams设给View。
  • 将View,ViewRootImpl以及LayoutParams对象添加到对应的集合中。
  • 调用ViewRootImpl的setView方法完成布局的刷新以及Window的添加。

ViewRootImpl的setView方法

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            //...
            requestLayout();
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
            //...
        }    
    }
}
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

在requestLayout方法中调用了scheduleTraversals,而这个scheduleTraversals就是View绘制流程的起点,在这里完成了View树的绘制。

mWindowSession是一个Binder对象,它的实现是Session

@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

最后调用WindowManagerService的addWindow方法完成了Window的添加。

Window的更新

Window的更新过程,直接来看WindowManagerGlobal的updateViewLayout方法

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

将新的LayoutParams设置给View,找到当前View的index索引,根据index移除旧的LayoutParams并将新的LayoutParams添加到集合中。调用ViewRootImpl的setLayoutParams方法

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

这里调用scheduleTraversals方法对布局进行重绘,完成了Window的更新。

Window的删除

WindowManagerGlobal的removeView方法

public void removeView(View view, boolean immediate) {
    //...
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate);
        //...
    }
}

获取要移除View的索引,然后调用removeViewLocked方法

private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index);
    View view = root.getView();
        //...
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

调用ViewRootImpl的die方法

boolean die(boolean immediate) {
    //...
    if (immediate && !mIsInTraversal) {
        doDie();
        return false;
    }
        //...
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

immediate参数表示是否要立即移除,即同步还是异步。我们一般采用异步移除的方式,异步移除使用mHandler发送MSG_DIE消息,然后返回true,在removeViewLocked方法中将该View添加到等待移除的列表mDyingViews中。mHandler中处理MSG_DIE消息的方法为doDie

void doDie() {
  //...
  if (mAdded) {
      dispatchDetachedFromWindow();
     }
    //...
  WindowManagerGlobal.getInstance().doRemoveView(this);
  //...
}
void dispatchDetachedFromWindow() {
    if (mView != null && mView.mAttachInfo != null) {
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
        mView.dispatchDetachedFromWindow();
    }
  //...
    try {
          mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
    //...
}

在dispatchDetachedFromWindow方法中会回调View的onDetachedFromWindow方法,以及调用Session的remove方法,该方法最终还是调用WMS的removeWindow方法完成Window的移除。

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);
        }
    }
    //...
}

最后在doRemoveView方法中刷新数据,移除对应的ViewRootImpl,LayoutParams以及将View从mDyingViews中移除,这就完成了Window的删除。

可以发现不管是添加,更新还是删除Window,最后都是在操作View,所以这就能证明Window并不是实际存在的,它是以View的形式存在,而Window是View的直接管理者。

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

推荐阅读更多精彩内容