Android GUI —WindowManager

WindowManager和Window的关系可以用下面一张图来描述


Activity.attach()

    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) {

        mWindow = new PhoneWindow(this, window, activityConfigCallback);

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

        mWindowManager = mWindow.getWindowManager();

    }

activity在ActivityThread中构造后,会立马调用attach方法,在attach方法中,创建了一个Window对象(具体是PhoneWindow),然后为当前window添加了WindowManager;

ActivityThrad.handleResumeActivity()

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
           ViewManager wm = a.getWindowManager();
            View decor = r.window.getDecorView();
                  wm.addView(decor, l);

    }

在Activity执行onResume的时候:ActivityThread会做以下几件事:

  • 获取windowManager
  • 获取Window的root View,即DecorView
  • wm.addView(decor, l),wm执行添加window操作

wm添加的实际内容是window的根View,所以说Window只是一个概念,上图PhoneWindow虚线表示不存在,实际上是DecorView

image.png

WindowManager

WindowManager的操作也就addView,upDateView,removeView这些,这些操作都是委托给内部的单例WindowMangerGlobal来实现的,直接来看mGlobal的实现即可:

在深入了解window操作view的实现之前,需要先认识下WindowMangerGlobal的三个重要的成员变量:

    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

WindowMangerGlobal是一个单例,所以在一个进程中只会存在一个mGlobal对象,这三个集合维护了所有的View,ViewRootImpl,WindowParams,并且他们三一一对应;
这里的View指Window的最parent的那个View,比如DecorView,mStatusBarView

WindowManagerGlobal.addView():

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

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

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

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
      
                root.setView(view, wparams, panelParentView);
    }
  • 更新mGlobal中维护的三个全局缓存
  • 创建ViewRootImpl对象,将需要添加的view(decorView,window,随便叫什么吧)交给ViewRootImpl去做事情;

这里引入了一个新的概念,ViewRootImpl,这是一个比较重要的角色,具体的功能如下:

  • 执行View的三大流程
  • 管理Surface
  • 负责同WMS通信
  • 转发input事件

关于ViewRootImpl的内容后续展开,下面继续学习WM的updateView 和 removeView操作

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);
        }
    }
  • 首先将需要更新的params设置到view对象中
  • 然后在找到这个view在那三个集合中的index(因为他们是一一对应的)
  • 拿到index 就能拿到对应的 viewRootImpl 和 old Params
  • 更新params 集合中的对应值为最新值
  • 调用ViewRootImpl的setLayoutParams()实现具体的update细节,后续在ViewRootImpl中展开

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

        }
    }

    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());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

全局除了那三个一一对应的集合外,还有一个集合:

    private final ArraySet<View> mDyingViews = new ArraySet<View>();

用于暂时保存即将remove的View;在每一次addView添加window的时候都会进行判断,如果即将添加的View在mDyingViews存在,就不会添加,并且让他去死(ViewRootImpl的doDie);

  • 根据immediate字段,如果true,就调用ViewRootImpl的die(),并根据die()的回调判断是否缓存到mDyingViews 集合,当再次添加这个window的时候再销毁;
WindowManagerGlobal.addView():
            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.
            }

ViewRootImpl

上文我们知道,WM的实际逻辑是交给了WMGlobal来处理的,而WMGlobal的操作Window的三个方法,都涉及到了ViewRootImpl这个类,下面我们来看看这个类的实现:

上面分析WMGlobal的时候我们遗留了四个问题:

  • ViewRootImpl.setView()
  • ViewRootImpl.setLayoutParams()
  • ViewRootImpl.die()
  • ViewRootImpl.doDie()

ViewRootImpl.setView()

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                }
    }

mWindowSession是由aidl文件生成的一个Binder对象,上面所有的操作都是在App进程进行的,到了这一步,viewRootImpl将通过自己内部的Binder(mWindowSession)将addView这个添加window的操作交给wms;


ViewRootImpl.setLayoutParams()

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

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();  // 1 barrier
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);  // 2 post
        }
    }

这里调用了scheduleTraversals():

  • 打开主线程的内存屏障,不了解内存屏障的同学可以先阅读这一篇:Handler 二期 —细节补充
  • 调用mChoreographer post 了一个Runnable

我们先来看看这个Runnable吧:

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

这段代码也很清晰,首先,关闭内存屏障,让当前的主线程继续loop,然后调用了一个非常核心的方法:performTraversals()在这个方法中,执行了我们View的三大流程,这个内存屏障的目的就是为了让这个performTraversals()立刻执行,不需要进过MessageQueue的排队;关于View三大流程的细节不在本篇展开;

mChoreographer.postCallback()在内部会调用postCallbackDelayedInternal()

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

这个方法的代码比较多,用一张图总结一下这个方法做的事情:



这个方法主要功能有两个,一个是schedule Vsync,还有一个是doFrame;

  • Vsync调用了另一个类,暂时不资到他是干什么的
  • doFrame()会去调用在ViewRootImpl中使用mChoreographer.postCallback()提交的那个Runnable任务,上面我们已经看过这个Runnable任务主要逻辑是调用了performTraversals(),于是就来到了View绘制的三大流程;

后续详解,我们再来看ViewRootImpl留下的最后一个问题:die()和doDie()

ViewRootImpl.die()

    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());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

ViewRootImpl.doDie()

 void doDie() {
        checkThread();
        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) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    mSurface.release();
                }
            }

            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

ViewRootImpl的处理window销毁的逻辑主要关注这几个:

  • dispatchDetachedFromWindow():清除当前window的view,listener等
  • mWindowSession.finishDrawing(mWindow):通知wms
  • mSurface.release():todo
  • WindowManagerGlobal.getInstance().doRemoveView(this):删除WMGlobal中对应的三个集合中的数据(ViewRootimpl,params,view)

三大流程

WMS通信

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

推荐阅读更多精彩内容