WindowManagerService探索——Window操作

本文基于Android 9.0

1. 前言

前面一篇文章简要的概括了一下WindowManager的架构,WindowManager归根到底还是对Window的操作,本文同样暂时不涉及WMS,具体详细的讲一讲Window的添加与更新操作,为后面剖析WMS打下扎实的基础。

2. Window

2.1 Winow的类型

Window的类型有很多种,在WindowMangaer中定义了很多的Type。其实细细的整理一下,Window的类型其实分为三大类,Application Window、Sub Window、 System Window,这三大类里面根据不同场景又分了很多子类供外部使用,详细的Type描述,可以在WindowManager的静态内部类LayoutParams中查看。接下来根据三种不同类型的Window,一一举例讲一下他们的添加流程。

2.2 Window的添加流程

2.2.1 Application Window的添加流程

Application Window最典型的就是Activity,下面就来讲一下Activity的Window部分操作的流程。在Activity启动过程中,如果Activity所在的进程不存在,那么系统就会创建一个新的进程,这个时候就会创建代表该应用主线程的实例ActivityThread,在Activity的启动过程中,会调用ActivityThread的handleResumeActivity方法,其代码如下:

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...

        // TODO Push resumeArgs into the activity for consideration
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        ...

            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }
          ...
    }

该方法中performResumeActivity就会触发Activity的onResume方法。这里可以看到通过ViewManager获取到wm对象,并把decor调用ViewManager的addView添加窗口。前面文章讲过,addView真正实现是在WindowManagerImpl中,其具体代码如下:

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

上一篇中讲到,其实addView也没有在WindowManagerImpl实现,而是继续委托给了WindowManagerGlobal来实现,这里来看一下WindowManagerGlobal中addView的逻辑:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
        
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

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

这里细心的同学可以发现三个列表,mViews、mRoots以及mParams,他们分别是View列表、ViewRootImpl列表和布局参数Params列表。这里比较特殊的就是ViewRootImpl,从代码中看,最后root调用setView来继续执行流程。这个ViewRootImpl到底是何方神圣呢?该类具有以下特点:

  • View树的根节点管理View树
  • 负责与WMS进程间通讯
  • 触发View的onMeasure、onLayout以及onDraw
  • 管理Surface
  • Input事件的中转

上面说道最后调用了ViewRootImpl的setView方法,其代码逻辑如下:

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
           ...
                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的addToDisplay方法,mWindowSession是一个binder对象,IWindowSession是一个Client端代理,Session端是在WMS中,所以Session的addToDisplay是运行在WMS中,这里可以看一下Session中addToDisplay的逻辑:

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
    }

在Session中addToDisplay方法中调用了WMS的addWindow方法,剩下的工作就全部交给WMS处理,这里先不细致讲解,大概的工作流程就是交给WMS后,WMS会给待添加的window分配surface,并确定window的显示次序,最后交给SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。这里将整个流程的时序图梳理一下:


Application Window添加过程时序图

2.2.2 Sub Window的添加流程

前面已经梳理了Application Window的流程,接下来快速梳理一下Sub Window的流程,Sub Window最典型的代表就是PopupWindow,众所周知,PopupWindow的display方法是showAsDropDown,这里来看一下showAsDropDown的具体逻辑:

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
        if (isShowing() || !hasContentView()) {
            return;
        }

        TransitionManager.endTransitions(mDecorView);

        attachToAnchor(anchor, xoff, yoff, gravity);

        mIsShowing = true;
        mIsDropdown = true;

        final WindowManager.LayoutParams p =
                createPopupLayoutParams(anchor.getApplicationWindowToken());
        preparePopup(p);

        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
                p.width, p.height, gravity, mAllowScrollingAnchorParent);
        updateAboveAnchor(aboveAnchor);
        p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;

        invokePopup(p);
    }

可以看到,首先通过createPopupLayoutParams来创建窗口属性,然后交给invokePopup来创建,创建逻辑如下:

private void invokePopup(WindowManager.LayoutParams p) {
        if (mContext != null) {
            p.packageName = mContext.getPackageName();
        }

        final PopupDecorView decorView = mDecorView;
        decorView.setFitsSystemWindows(mLayoutInsetDecor);

        setLayoutDirectionFromAnchor();

        mWindowManager.addView(decorView, p);

        if (mEnterTransition != null) {
            decorView.requestEnterTransition(mEnterTransition);
        }
    }

这里又看到熟悉的身影,调用的WindowManager的addView,接下来流程就和前面Application Window添加的流程大体一致了,这里也不再去重复描述了。

2.2.3 System Window的添加流程

System Window流程,这里我们选取一个常见的System Window——NavigationBar来讲一下,NavigationBar是SystemUI的重要组成部分,主要功能是用来存放Home键、Back键以及最近任务键,并响应对应的操作。Navigationbar的创建在SystemUI的Statusbar类中,当SystemUI初始化的时候会调用makeStatusBarView方法,该方法中会进行创建Navigationbar的操作,具体逻辑如下:

protected void makeStatusBarView() {
    ...
    try {
            boolean showNav = mWindowManagerService.hasNavigationBar();
            if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
            if (showNav) {
                createNavigationBar();
            }
        } catch (RemoteException ex) {
            // no window manager? good luck with that
        }
    ...
}

protected void createNavigationBar() {
        mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
            mNavigationBar = (NavigationBarFragment) fragment;
            if (mLightBarController != null) {
                mNavigationBar.setLightBarController(mLightBarController);
            }
            mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
        });
 }

可以看到NavigationBar其实是一个Fragment,循序Fragment的生命周期,这里深入到creat的方法看看具体实现:

    public static View create(Context context, FragmentListener listener) {
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT);
        lp.token = new Binder();
        lp.setTitle("NavigationBar");
        lp.accessibilityTitle = context.getString(R.string.nav_bar);
        lp.windowAnimations = 0;

        View navigationBarView = LayoutInflater.from(context).inflate(
                R.layout.navigation_bar_window, null);

        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
        if (navigationBarView == null) return null;

        context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
        FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
        NavigationBarFragment fragment = new NavigationBarFragment();
        fragmentHost.getFragmentManager().beginTransaction()
                .replace(R.id.navigation_bar_frame, fragment, TAG)
                .commit();
        fragmentHost.addTagListener(TAG, listener);
        return navigationBarView;
    }

可以看到这里设置的LayoutParams中有一个很重要的信息:WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,然后通过context的getSystemService来调用WindowManager的addView方法,这里携带了TYPE_NAVIGATION_BAR进行的addView,在系统部分,除了会像前面两种窗口一样的流程,还会有额外的判断逻辑,保证NavigationBar正常的窗口功能需求。

2.3 Window的更新流程

Window收到更新操作的时候是如何执行流程的呢?这里也来一起看下对应的流程,首先当调用updateViewLayout之后,最终还是会委托给WindowManagerGlobal的updateViewLayout,具体逻辑如下:

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

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

首先会重新设置View的LayoutParam,然后找到View在View树中的节点,更新mParams列表,最后通过ViewRootImpl中setLayoutParams继续往下执行流程。在setLayoutParams中会调用ViewRootImpl的scheduleTraversals方法,具体逻辑如下:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这里通过mChoreographer的postCallback发起了一个回调,在往下将之前,先介绍一下Choreographer,这个是用于接收显示系统的Vsync信号,在下一帧渲染时执行一些操作,这里就是将在下一帧渲染时执行
mTraversalRunnable,具体逻辑如下:

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

在TraversalRunnable中的run方法里执行了doTraversal,逻辑如下:

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

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            if (ViewDebugManager.DEBUG_SCHEDULETRAVERSALS)
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "doTraversal");
            try { 
            if(com.journey.system.JourneyDebugFeature.DEBUG_INVALIDATE && (!com.journey.system.JourneyDebugFeature.DEBUG_INVALIDATE_LOW)) {
                com.journey.system.JourneyDebugFeature.enableLogViewInvalidate(false);}

            performTraversals();
            } finally {
                if (ViewDebugManager.DEBUG_SCHEDULETRAVERSALS) Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

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

最终调用了performTraversals方法,该方法就是让View树执行View的工作流程,逻辑如下:

private void performTraversals() {
    ...
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
    ...
    if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    ...
    if (didLayout) {
            performLayout(lp, mWidth, mHeight);
        ...
    }
    ...
     if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }

            performDraw();
        }
    ...
}

这里需要关注以下几个方法:1.relayoutWindow方法会最终调用IWindowSession的relayout方法让WMS来更新Window视图,2.performMeasure、performLayout以及performDraw分别对应者view的measure、Layout以及draw操作,这样同时执行了Window视图的更新,以及View的工作流程,最后实现了Window的更新。

到这里,Window的添加和更新操作Window到WinddowManger以及WindowManager如何向WMS通讯的流程已经梳理完毕,至于删除操作我们将在WMS解析的章节里面进一步详细介绍。

本文已独家授权公众号ApeClub使用,更多好文请关注ApeClub。

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

推荐阅读更多精彩内容