Android顯示框架:Android應用視窗的管理者WindowManager

蘇策發表於2017-11-30

關於作者

郭孝星,程式設計師,吉他手,主要從事Android平臺基礎架構方面的工作,歡迎交流技術方面的問題,可以去我的Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。

第一次閱覽本系列文章,請參見導讀,更多文章請參見文章目錄

文章目錄

  • 一 Window的新增流程
  • 二 Window的刪除流程
  • 三 Window的更新流程

The interface that apps use to talk to the window manager.

WindowManager是應用與視窗管理服務WindowManagerService互動的介面,WindowManagerService是位於Framework層的視窗管理服務,它的職責是管理系統中的所有視窗,也就是Window,
關於Window的介紹,我們在文章03Android顯示框架:Android應用檢視的管理者Window已經
詳細分析過,通俗來說,Window就是手機上一塊顯示區域,也就是Android中的繪製畫布Surface,新增一個Window的過程,也就是申請分配一塊Surface的過程。而整個流程的管理者正是WindowManagerService。

Window在WindowManagerService的管理下,有序的顯示在螢幕上,構成了多姿多彩的使用者介面,關於Android的整個視窗系統,可以用下圖來表示:

Android顯示框架:Android應用視窗的管理者WindowManager

  • WindowManager:應用與視窗管理服務WindowManagerService互動的介面
  • WindowManagerService:視窗管理服務,該服務執行在一個單獨的程式中,因此WindowManager與WindowManagerService的互動也是一個IPC的過程。
  • SurfaceFlinger:SurfaceFlinger服務執行在Android系統的System程式中,它負責管理Android系統的幀緩衝區(Frame Buffer),Android裝置的螢幕被抽象為一個
    幀緩衝區,而Android系統中的SurfaceFlinger服務就是通過向這個幀緩衝區寫入內容來繪製應用程式的使用者介面的。
  • Surface:每個顯示介面的視窗都是一個Surface。

WindowManager是一個介面,繼承於ViewManager,實現類是WindowManagerImpl,實際上我們常用的功能,也是定義在ViewManager裡的。

public interface ViewManager{
    //新增View
    public void addView(View view, ViewGroup.LayoutParams params);
    //更新View
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    //刪除View
    public void removeView(View view);
}複製程式碼

WindowManager可以通過Context來獲取,WindowManager也會和其他服務一樣在開機時註冊到ContextImpl裡的map容器裡,然後通過他們的key來獲取。

windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);複製程式碼

WindowManager的實現類是WindowManagerImpl,在WindowManagerImpl內部實際的功能是有WindowManagerGlobal來完成的,我們直接來分析它裡面這三個方法的實現。

一 Window的新增流程

public final class WindowManagerGlobal {

     public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
            //校驗引數的合法性
            ...

            //ViewRootImpl封裝了View與WindowManager的互動力促
            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);
                        }
                    }
                }

                //通過上下文構建ViewRootImpl
                root = new ViewRootImpl(view.getContext(), display);

                view.setLayoutParams(wparams);

                //mViews儲存著所有Window對應的View物件
                mViews.add(view);
                //mRoots儲存著所有Window對應的ViewRootImpl物件
                mRoots.add(root);
                //mParams儲存著所有Window對應的WindowManager.LayoutParams物件
                mParams.add(wparams);
            }

            // do this last because it fires off messages to start doing things
            try {
                //呼叫ViewRootImpl.setView()方法完成Window的新增並更新介面
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                synchronized (mLock) {
                    final int index = findViewLocked(view, false);
                    if (index >= 0) {
                        removeViewLocked(index, true);
                    }
                }
                throw e;
            }
        }

}複製程式碼

在這個方法裡有三個重要的成員變數:

  • mViews儲存著所有Window對應的View物件
  • mRoots儲存著所有Window對應的ViewRootImpl物件
  • mParams儲存著所有Window對應的WindowManager.LayoutParams物件

這裡面提到了一個我們不是很熟悉的類ViewRootImpl,它其實就是一個封裝類,封裝了View與WindowManager的互動方式,它是View與WindowManagerService通訊的橋樑。
最後也是呼叫ViewRootImpl.setView()方法完成Window的新增並更新介面。

我們來看看這個方法的實現。

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {

     public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                if (mView == null) {
                    mView = view;

                    //引數校驗與預處理
                    ...

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

                    //1. 呼叫requestLayout()完成介面非同步繪製的請求
                    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();
                        //2. 建立WindowSession並通過WindowSession請求WindowManagerService來完成Window新增的過程
                        //這是一個IPC的過程。
                        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
                    } catch (RemoteException e) {
                        mAdded = false;
                        mView = null;
                        mAttachInfo.mRootView = null;
                        mInputChannel = null;
                        mFallbackEventHandler.setView(null);
                        unscheduleTraversals();
                        setAccessibilityFocus(null, null);
                        throw new RuntimeException("Adding window failed", e);
                    } finally {
                        if (restore) {
                            attrs.restore();
                        }
                    }
                    ...
            }
        }    
}複製程式碼

這個方法主要做了兩件事:

  1. 呼叫requestLayout()完成介面非同步繪製的請求, requestLayout()會去呼叫scheduleTraversals()來完成View的繪製,scheduleTraversals()方法將一個TraversalRunnable提交到工作佇列中執行View的繪製。而
    TraversalRunnable最終呼叫了performTraversals()方法來完成實際的繪製操作。提到performTraversals()方法我們已經很熟悉了,在文章02Android顯示框架:Android應用檢視的載體View
    我們已經詳細的分析過它的實現。
  2. 建立WindowSession並通過WindowSession請求WindowManagerService來完成Window新增的過程這是一個IPC的過程,WindowManagerService作為實際的視窗管理者,視窗的建立、刪除和更新都是由它來完成的,它同時還負責了視窗的層疊排序和大小計算
    等工作。

注:在文章02Android顯示框架:Android應用檢視的載體View
我們已經詳細的分析過performTraversals()方法的實現,這裡我們再簡單提一下:

  1. 獲取Surface物件,用於圖形繪製。
  2. 呼叫performMeasure()方法測量檢視樹各個View的大小。
  3. 呼叫performLayout()方法計算檢視樹各個View的位置,進行佈局。
  4. 呼叫performMeasure()方法對檢視樹的各個View進行繪製。

既然提到WindowManager與WindowManagerService的跨程式通訊,我們再講一下它們的通訊流程。Android的各種服務都是基於C/S結構來設計的,系統層提供服務,應用層使用服務。WindowManager也是一樣,它與
WindowManagerService的通訊是通過WindowSession來完成的。

  1. 首先呼叫ServiceManager.getService("window")獲取WindowManagerService,該方法返回的是IBinder物件,然後呼叫IWindowManager.Stub.asInterface()方法將WindowManagerService轉換為一個IWindowManager物件。
  2. 然後呼叫openSession()方法與WindowManagerService建立一個通訊會話,方便後續的跨程式通訊。這個通訊會話就是後面我們用到的WindowSession。

基本上所有的Android系統服務都是基於這種方式實現的,它是一種基於AIDL實現的IPC的過程。關於AIDL讀者可自行查閱資料。

public final class WindowManagerGlobal {

    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    //獲取WindowManagerService物件,並將它轉換為IWindowManager型別
                    IWindowManager windowManager = getWindowManagerService();
                    //呼叫openSession()方法與WindowManagerService建立一個通訊會話,方便後續的
                    //跨程式通訊。
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                //呼叫ServiceManager.getService("window")獲取WindowManagerService,該方法返回的是IBinder物件
                //,然後呼叫IWindowManager.Stub.asInterface()方法將WindowManagerService轉換為一個IWindowManager物件
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    sWindowManagerService = getWindowManagerService();
                    ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }
 }複製程式碼

二 Window的刪除流程

Window的刪除流程也是在WindowManagerGlobal裡完成的。

public final class WindowManagerGlobal {

   public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            //1. 查詢待刪除View的索引
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            //2. 呼叫removeViewLocked()完成View的刪除, removeViewLocked()方法
            //繼續呼叫ViewRootImpl.die()方法來完成View的刪除。
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

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

}複製程式碼

我們再來看看ViewRootImpl.die()方法的實現。

public final class ViewRootImpl implements ViewParent,

        View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
      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.

            //根據immediate引數來判斷是執行非同步刪除還是同步刪除
            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());
            }
            //如果是非同步刪除,則傳送一個刪除View的訊息MSG_DIE就會直接返回
            mHandler.sendEmptyMessage(MSG_DIE);
            return true;
        }

        void doDie() {
            checkThread();
            if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
            synchronized (this) {
                if (mRemoved) {
                    return;
                }
                mRemoved = true;
                if (mAdded) {
                    //呼叫dispatchDetachedFromWindow()完成View的刪除
                    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;
            }
            //重新整理資料,將當前移除View的相關資訊從我們上面說過了三個列表:mRoots、mParms和mViews中移除。
            WindowManagerGlobal.getInstance().doRemoveView(this);
        }

        void dispatchDetachedFromWindow() {
                //1. 回撥View的dispatchDetachedFromWindow方法,通知該View已從Window中移除
                if (mView != null && mView.mAttachInfo != null) {
                    mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
                    mView.dispatchDetachedFromWindow();
                }

                mAccessibilityInteractionConnectionManager.ensureNoConnection();
                mAccessibilityManager.removeAccessibilityStateChangeListener(
                        mAccessibilityInteractionConnectionManager);
                mAccessibilityManager.removeHighTextContrastStateChangeListener(
                        mHighContrastTextManager);
                removeSendWindowContentChangedCallback();

                destroyHardwareRenderer();

                setAccessibilityFocus(null, null);

                mView.assignParent(null);
                mView = null;
                mAttachInfo.mRootView = null;

                mSurface.release();

                if (mInputQueueCallback != null && mInputQueue != null) {
                    mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
                    mInputQueue.dispose();
                    mInputQueueCallback = null;
                    mInputQueue = null;
                }
                if (mInputEventReceiver != null) {
                    mInputEventReceiver.dispose();
                    mInputEventReceiver = null;
                }

                //呼叫WindowSession.remove()方法,這同樣是一個IPC過程,最終呼叫的是
                //WindowManagerService.removeWindow()方法來移除Window。
                try {
                    mWindowSession.remove(mWindow);
                } catch (RemoteException e) {
                }

                // Dispose the input channel after removing the window so the Window Manager
                // doesn't interpret the input channel being closed as an abnormal termination.
                if (mInputChannel != null) {
                    mInputChannel.dispose();
                    mInputChannel = null;
                }

                mDisplayManager.unregisterDisplayListener(mDisplayListener);

                unscheduleTraversals();
            }

}複製程式碼

我們再來總結一下Window的刪除流程:

  1. 查詢待刪除View的索引
  2. 呼叫removeViewLocked()完成View的刪除, removeViewLocked()方法繼續呼叫ViewRootImpl.die()方法來完成View的刪除。
  3. ViewRootImpl.die()方法根據immediate引數來判斷是執行非同步刪除還是同步刪除,如果是非同步刪除則則傳送一個刪除View的訊息MSG_DIE就會直接返回。
    如果是同步刪除,則呼叫doDie()方法。
  4. doDie()方法呼叫dispatchDetachedFromWindow()完成View的刪除,在該方法裡首先回撥View的dispatchDetachedFromWindow方法,通知該View已從Window中移除,
    然後呼叫WindowSession.remove()方法,這同樣是一個IPC過程,最終呼叫的是WindowManagerService.removeWindow()方法來移除Window。

三 Window的更新流程

public final class WindowManagerGlobal {

    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的LayoutParams引數
        view.setLayoutParams(wparams);

        synchronized (mLock) {
            //查詢Viewd的索引,更新mParams裡的引數
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            //呼叫ViewRootImpl.setLayoutParams()完成重新佈局的工作。
            root.setLayoutParams(wparams, false);
        }
    }   
}複製程式碼

我們再來看看ViewRootImpl.setLayoutParams()方法的實現。

public final class ViewRootImpl implements ViewParent,

   void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
           synchronized (this) {
               //引數預處理
               ...

                //如果是新View,呼叫requestLayout()進行重新繪製
               if (newView) {
                   mSoftInputMode = attrs.softInputMode;
                   requestLayout();
               }

               // Don't lose the mode we last auto-computed.
               if ((attrs.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
                       == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
                   mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
                           & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
                           | (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
               }

               mWindowAttributesChanged = true;
               //如果不是新View,呼叫requestLayout()進行重新繪製
               scheduleTraversals();
           }
       } 
}複製程式碼

Window的更新流程也和其他流程相似:

  1. 更新View的LayoutParams引數,查詢Viewd的索引,更新mParams裡的引數。
  2. 呼叫ViewRootImpl.setLayoutParams()方法完成重新佈局的工作,在setLayoutParams()方法裡最終會呼叫scheduleTraversals()
    進行解碼重繪製,scheduleTraversals()後續的流程就是View的measure、layout和draw流程了,這個我們在上面已經說過了。

通過上面分析,我們瞭解了Window的新增、刪除和更新流程。那麼我們來思考一個問題:既然說是Window的新增、刪除和更新,那為什麼方法名是addView、updateViewLayout、removeView,Window的本質是什麼??

事實上Window是一個抽象的概念,也就是說它並不是實際存在的,它以View的形式存在,每個Window都對應著一個View和一個ViewRootImpl,Window與View通過ViewRootImpl來建立聯絡。推而廣之,我們可以理解
WindowManagerService實際管理的也不是Window,而是View,管理在當前狀態下哪個View應該在最上層顯示,SurfaceFlinger繪製也同樣是View。

好了本篇文章的內容到這裡就講完了,在這篇文章中我們側重分析Android視窗服務Client這一側的實現,事實上更多的內容是在Server這一側,也就是WindowManagerService。只不過在日常的開發中我們較少
接觸到WindowManagerService,它屬於系統的內部服務,就暫時不做進一步的展開。總體上來說,本系列文章的目的還是在於更好的服務應用層的開發者,等到關於Android應用層Framework實現原理分析完成以後,
我們再進一步深入,去分析系統層Framework的實現。

相關文章