WindowManager——Android视图的真正管理者

WMS、WindowManager与Window之间的联系

  • Window是一个抽象类,在Andorid系统中其仅有一个具体实现类即PhoneWindow,负责对View进行管理。
  • WindowManager是一个接口类,继承自接口ViewManager,它的实现类为WindowManagerImpl。顾名思义其是用来管理Window的,负责Window的添加、更新和删除操作。
  • WindowManager通过Binder与WindowManagerService进行跨进程通信,把具体的实现工作交给WindowManagerService来完成。

简单的理解就是:Window是View的载体,通过WindowManager实现对Window增删改的行为,具体的实现操作由WindowManager通过Binder方式交由WMS去处理。

三者功能关联图.png

WindowManager是如何与Window进行关联的?

由于在Activity的构建过程中会创建与Acitvity相对应的Window,所以下面我们从Activity的attach方法着手,一步步深入源码来查看Window是如何与WindowManager建立联系的。

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        //①实例化PhoneWindow对象
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }
        //②给Window设置WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
    }

我给上述代码中的两个关键位置加上了中文注释:

  • ①mWindow = new PhoneWindow(this, window, activityConfigCallback);
    前面我们解释过,PhoneWindow是Android中唯一一个实现了Window的类,所以此处通过实例化PhoneWindow的方式给Activity的mWindow变量赋值。

  • ② mWindow.setWindowManager(
    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
    mToken, mComponent.flattenToString(),
    (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    此处即是通过调用Window的setWindowManager方法建立Window和WindowManager之间的联系

接着看setWindowManager方法的具体实现

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        //Window的实际管理者为WindowManagerImpl
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

该方法会先判断传来的WindowManager是否为null。若为null的话,会再次获取注册在系统中的Window服务,然后将WindowManager向下转型为WindowManagerImpl对象并调用其createLocalWindowManager方法,给WindowManagerImpl中的mParentWindow变量赋值

至此,Window中的mWindowManager变量持有WindowManagerImpl对象,WindowManagerImpl中的mParentWindow变量持有对应的PhoneWindow对象,二者进行了相互关联。

WindowManager是如何对Window进行操作的?

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对Window的操作可以理解为就是对View的操作,即View的添加、更新以及删除。

Tip:这里就存在一个问题了,既然WindowManager的操作都是面向View的,那Window的存在还有什么意义?

答:Window的类型有很多种,比如应用程序窗口、系统错误窗口、输入法窗口、PopupWindow、Toast、Dialog等。总的来说Window按功能划分可以分为三大类,分别是应用程序窗口、子窗口和系统窗口。每个大类型中又包含了很多种类型,它们的存在很好的满足Andorid系统中各式各样的功能对不同窗口的需求。起主要作用的有三种属性:Type(主要用来管理窗口的显示次序),Flag(用于控制Window的显示(如全屏、保持常亮等))和SoftInputMode(控制软键盘的显示行为)。

Window三大类型.png

下面就以addView为例来详细剖析WindowManager是如何对Window进行添加操作的。关于addView方法的具体实现我们要在WindowManager的实现类WindowManagerImpl中查看。

    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的操作全部都委托给了WindowManagerGlobal去处理,这里用到的是桥接模式。而WindowManagerGlobal是通过单例模式获取到的,也就是说一个进程中只存在一个WindowManagerGlobal实例。

下面继续看WindowManagerGlobal的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、ViewRoot以及View的参数三者放入WindowManagerGlobal的数组中保存
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                //关键代码!!!!
                //③将添加View的操作交至ViewRoot处理
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

上述代码有三个关键点,①实例化一个ViewRootImpl对象;②将View、布局参数、ViewRoot三者添加进WindowManagerGlobal所维护的数组中;③将添加View的操作交由ViewRoot继续处理

下面继续看ViewRootImpl的setView方法

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {

                ......
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                //第一次开始布局
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //
                    //通过IPC方式添加视图
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                }
            ......
        }
    }

上述代码首先调用requestLayout方法,该方法最终会调用performTraversals来开始第一次视图绘制,具体的绘制过程参考另一篇文章。随后,便是调用mWindowSession的addToDisplay方法以IPC的方式将添加的视图传递到WMS。
其中mWindow是IWindow.Stub的子类所构造的对象,其内部持有ViewRootImpl的弱引用。

下图以系统窗口StatusBar为例,展示了视图的添加流程。


StatusBar添加流程图.png

WMS是如何对Window进行操作的?

上述过程WindowManager将添加Window的操作通过ViewRootImpl中所持有的IWindowSession以Binder的形式传递到了WMS进程。由于WMS较为复杂,下面我们就简要的描述一下WMS针对addWindow这个方法做了哪些事。

  • ①WMS对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会再执行后面的代码逻辑。
  • ②WindowToken相关的处理,比如有的窗口类型需要提供WindowToken,没有提供的话就不会执行后面的代码逻辑,有的窗口类型则由WMS隐式创建WindowToken。(每个Activity都对应一个WindowToken)
  • ③WindowState的创建和相关处理,将WindowToken和WindowState相关联。
  • ④创建和配置DisplayContent,完成窗口添加到系统前的准备工作。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344