WindowManager家族的复杂关系

443 阅读6分钟

1. 前话类比关系:

今天要讲的就是Window、WindowManager、WindowManagerImpl和WindowManagerGlobal之间的复杂关系,他们之间的关系说复杂其实也不复杂,我直接用个类比,就很好理解了。 举个例子哈,张三创业了,成立一个公司,很成功,然后他们就成为一个大公司,那他作为董事长也不需要自己去管理这一切了,于是他雇了一个CEO去管理整个企业,但是CEO他也不会什么事都管啊,总不能说今天招个员工明天优化一个员工都要给他打招呼对吧。 于是又有了给CEO干活的各个部门的领导,部门领导也得学会驭人之道啊,总不能什么事都亲力亲为吧,所以手下有几个小员工, 但是自有员工心想脏活累活不能干啊,于是把脏活累活都甩给了外包员工。 故事说完了,很心酸,都是打工人还非要分个三六九等,但是现实就是这么滑稽,甚至连代码都是这样。 上面的故事中,董事长->Activity、CEO->WindowManger、小领导->WindowManagerImpl、自有员工->WindowManagerGlobal、合作员工->WindowManagerService; 是不是例子中还有你的影子?真扎心。 那么Window是什么呢,Window就相当于一个抽象概念,就比如是张三的这个公司。Window刚好就是一个抽象类。这么一说大家应该也知道了,所谓的WindowManager其实就是帮助Activity去管理Window的一个东西,然后他啥也不会干,活都交给了WindowManagerImpl, WindowManagerImpl其实也没好哪去,拿到任务立马转发给手下WindowManagerGlobal和外包员工,最后功能的实现都是由底层员工和外包员工完成的。

2. Window的抽象概念:

前面的博客说过window就像画纸,那么我们在绘画的过程中是不是可以有多张画纸,叠在画板上。 这就是window高度属性的具象化场景,高度越高,就会显示在越上层,而在下层的window则会被遮挡。就像悬浮窗场景,window就会遮挡住正常的显示界面。

3. Window的管理

Window既然会有多个,比如我们的Toast弹窗,那怎么在当前界面上添加window呢?Activity里面有方法能够添加Window嘛?我们搜下addWindow和addView,都没有方法定义,而且前面说了,Activity是董事长,这点小事应该也不需要他做什么,而且添加view其实就是在window上添加,所以只要让Window操作就行了,于是继续在Window.java中搜索,依旧没有结果。这是因为Window就是一个抽象的类,我们要管理Window,自然就想到了WindowManager了,顾名思义嘛,window的管理者。 所以进入WindowManager.java直接看代码

 public interface WindowManager extends ViewManager 
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

所以其实WindowManager是继承自ViewManager的,也就是说WindowManger其实也能管理View,然后在这个基础上再管理Window。 这也和上面说的契合了,我们要Activity委托phoneWindow来管理界面上的View。 这里看下主要是三个功能,添加View,更新view的布局,移除View。

4. addView:文章最后有附上UML类图便于理解。

本文就以addView为例,说明整个WindowManager家族的关系。 根据上文可知,WindowManager可以添加View,所以看看WindowManager中是否存在addView方法。 结果一看没有,那就去子类中寻找,毕竟WindowManger只是一个接口, 往下找找到了WindowManagerImpl,他是唯一实现WindowManager的类,其中刚好有addView的方法体。

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

这里可以看到,方法的具体实现其实并没有给出,只是在实现时调用了WindowManagerGlobal的addView方法,于是继续跟踪,进入WindowManagerGlobal类。这里方法实现太长,只看关键部分。

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
…………………
if (windowlessSession == null) {
    root = new ViewRootImpl(view.getContext(), display);
} else {
    root = new ViewRootImpl(view.getContext(), display,
            windowlessSession, new WindowlessWindowLayout());
}

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, userId);
} catch (RuntimeException e) {
    final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
    // BadTokenException or InvalidDisplayException, clean up.
    if (viewIndex >= 0) {
        removeViewLocked(viewIndex, true);
    }
    throw e;
}
}
这里可以看到,在addView方法里新建了一个ViewRootImpl,具体可到上篇博客中查看,这里主要还是去看怎么添加view,于是进入这个ViewRootImpl类的setView方法中查看实现。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
……………………………………
   res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
        getHostVisibility(), mDisplay.getDisplayId(), userId,
        mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
        mTempControls, attachedFrame, compatScale);
……………………………………
}

可以看到,主要是通过调用mWindowSession的addToDispalyAsUser方法完成的addView功能,这里mWindowSession其实还是一个IADL接口,这里功能是通过IPC通信去访问SystemServer中的WindowManagerService,调用相应的接口。

至于此处怎么往上查看addToDispalyAsUser方法的实现也比较简单,首先看下mWindowSession是什么类型的,通过查看发现是IWindowSession,所以这里需要查看是什么地方实现了IwindowSession这个AIDL接口,于是可以搜索IWindowSession.stub,这是实现AIDL接口的标准流程,后面有机会还会讲下Binder,具体为啥就在那里再讲吧。 通过查找发现了一个新的类,Session类,是他实现了IWindowSession.Stub,而addToDispalyAsUser方法:

public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
        InputChannel outInputChannel, InsetsState outInsetsState,
        InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
        float[] outSizeCompatScale) {
    return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
            requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
            outAttachedFrame, outSizeCompatScale);
}

可以看到方法的实现还是交给了WindowManagerService的addWindow方法,这里就不继续看具体实现了。

5. 流程梳理:

于是这一套历程走下来,是不是发现了一个问题?说好的添加View怎么变成了添加Window了?这里我也比较疑惑,我的理解是,Window他其实是一个View的载体,但是子啊系统侧看来其实他和View属于接近的概念,于是我们就直接用addView这个方法来添加Window,从代码上看addView方法是从ViewManager那继承过来的,但是可别忘了,继承ViewManager的可不止WindowManager一个, 这里我们随便找一个真的addView的方法,就比如TableLayout中的addView方法,发现这里有关键字@override,故往上找,发现往上分别是LinearLayout 和ViewGroup,所以一看到ViewGroup,到这里看到了 public abstract class ViewGroup extends View implements ViewParent, ViewManager { 所以这里也有个addView方法是从ViewGroup这里继承来的,所以ViewGroup其实和WindowManager的addView方法都继承自ViewManager,他们就像兄弟一样的关系,只不过ViewGroup里的addView方法添加的是View,WindowManager里的addView方法添加的是Window罢了。这里也解释了为啥addView的流程中会出现新建ViewRootImpl对象的疑问了对吧。 当然这里只是我的理解,欢迎大家一起沟通交流,Android是数以万计的开发者的心血,我也不可能几天就参透了其中奥秘。 下面是我画的UML的类关系图,如图所示,这里的关系比较复杂,结合上面的思路来看可能会轻松点。

在这里插入图片描述

在这里插入图片描述