Window, WindowManager和WindowManagerService之間的關係

rain9155發表於2019-07-20

前言

上面3個名詞在開發中經常聽到,在Android開發中,Window是所有檢視的載體,如Activity,Dialog和Toast的檢視,我們想要對Window進行新增和刪除就要通過WindowManager來操作,而WindowManager就是通過Binder與WindowManagerService進行跨程式通訊,把具體的實現工作交給WindowManagerService(下面簡稱WMS)。下面分別介紹它們,理清它們的基本脈絡。

本文基於Android8.0, 相關原始碼位置如下:
frameworks/base/core/java/android/view/*.java(*代表Window, WindowManager, ViewManager, WindowManagerImpl,WindowManagerGlobal, ViewRootImpl)
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java	
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java	
frameworks/base/services/core/java/com/android/server/wm/Session.java
複製程式碼

Window

1、Window是什麼

Window在Android開發中是一個視窗的概念,它是一個抽象類,我們開啟Window,如下:

public abstract class Window {
    public static final int FEATURE_NO_TITLE = 1;
    public static final int FEATURE_CONTENT_TRANSITIONS = 12;
    //...
     public abstract View getDecorView();
     public abstract void setContentView(@LayoutRes int layoutResID);
     public abstract void setContentView(View view);
     public abstract void setContentView(View view, ViewGroup.LayoutParams params);
     public <T extends View> T findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }
    //...
}
複製程式碼

可以看到裡面有我們熟悉的一些欄位和方法,以Activity對應的Window為例,具體的實現類是PhoneWindow,在PhoneWindow中有一個頂級View---DecorView,繼承自FrameLayout,我們可以通過getDecorView()獲得它,當我們呼叫Activity的setContentView時,其實最終會呼叫Window的setContentView,當我們呼叫Activity的findViewById時,其實最終呼叫的是Window的findViewById,這也間接的說明了Window是View的直接管理者。但是Window並不是真實存在的,它更多的表示一種抽象的功能集合,View才是Android中的檢視呈現形式,繪製到螢幕上的是View不是Window,但是View不能單獨存在,它必需依附在Window這個抽象的概念上面,Android中需要依賴Window提供檢視的有Activity,Dialog,Toast,PopupWindow,StatusBarWindow(系統狀態列),輸入法視窗等,因此Activity,Dialog等檢視都對應著一個Window。

2、Window的型別(應用視窗,子視窗,系統視窗)與層級

Window的型別type被定義在WindowManager中的靜態內部類LayoutParams中,如下:

public interface WindowManager extends ViewManager {
    //...
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        //應用程式視窗type值
        public static final int FIRST_APPLICATION_WINDOW = 1;//代表應用程式視窗的起始值
        public static final int TYPE_BASE_APPLICATION   = 1;//視窗的基礎值,其他視窗的type值要大於這個值
        public static final int TYPE_APPLICATION        = 2;//普通應用程式視窗,token必須設定為Activity的token來指定視窗屬於誰
        public static final int TYPE_APPLICATION_STARTING = 3;
        public static final int TYPE_DRAWN_APPLICATION = 4;
        public static final int LAST_APPLICATION_WINDOW = 99;//代表應用程式視窗的結束值
        
        //子視窗type值
        public static final int FIRST_SUB_WINDOW = 1000;//代表子視窗的起始值
        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
        public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
        public static final int LAST_SUB_WINDOW = 1999;//代表子視窗的結束值
        
        //系統視窗的type值
        public static final int FIRST_SYSTEM_WINDOW     = 2000;//代表系統視窗的起始值
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;//系統狀態列
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;//搜尋條視窗
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;//通話視窗
        //...
        public static final int LAST_SYSTEM_WINDOW      = 2999;//代表系統視窗結束值
    }
}
複製程式碼

LayoutParams中以TYPE開頭的值有很多,但總體可以分為3類:

  • 應用程式視窗:type值範圍是1~99,Activity就是一個典型的應用程式視窗,type值是TYPE_BASE_APPLICATION,WindowManager的LayoutParams預設type值是TYPE_APPLICATION。
  • 子視窗:type值範圍是1000~1999,PupupWindow就是一個典型的子視窗,type值是TYPE_APPLICATION_PANEL,子視窗不能獨立存在,必須依附於父視窗
  • 系統視窗:type值範圍是2000~2999,系統視窗的型別很多,上面並沒有全部列舉出來,系統狀態列就是一個典型的系統視窗,type值是TYPE_STATUS_BAR,與應用程式視窗不同的是,系統視窗的建立是需要宣告許可權的。

type值決定了決定了Window顯示的層級(z-ordered),即在螢幕Z軸方向的顯示次序,一般情況下type值越大,則視窗顯示的越靠前,在Window的3種型別中,應用程式視窗的層級範圍是1~99,子視窗的層級範圍是1000~1999,系統視窗的層級範圍是2000~2999,層級範圍對應著type值,如果想要Window位於所有的Window上,採用較大的層級即可,例如系統層級。

3、Window的屬性

Window的型別flag同樣被定義在WindowManager中的靜態內部類LayoutParams中,如下:

public interface WindowManager extends ViewManager {
    //...
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;
        public static final int FLAG_DIM_BEHIND        = 0x00000002;
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
		//...
    }
}
複製程式碼

LayoutParams中定義的flag屬性同樣很多,這裡挑幾個常見的講解:

  • FLAG_ALLOW_LOCK_WHILE_SCREEN_ON:只要視窗對使用者可見,就允許在螢幕開啟狀態下鎖屏。
  • FLAG_KEEP_SCREEN_ON: 只要視窗對使用者可見,螢幕就一直亮著。
  • FLAG_SHOW_WHEN_LOCKED:視窗可以在鎖屏的介面上顯示。
  • FLAG_NOT_FOCUSABLE:視窗不能獲取焦點,也不能接受任何輸入事件,此標誌同時會啟用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點的視窗。
  • FLAG_NOT_TOUCH_MODAL:當前視窗區域以外的觸控事件會傳遞給底層的視窗,當前視窗區域內的觸控事件則自己處理,一般來說都要開啟此標記,否則其他Window將無法收到單機事件。
  • FLAG_NOT_TOUCHABLE:視窗不接收任何觸控事件

可以看到LayoutParams中的type和flag非常重要,可以控制Window的顯示特性。知道了Window的相關資訊,就能更好的瞭解WindowManager。

WindowManager

WindowManager是一個介面,裡面常用的方法有:新增View,更新View和刪除View,WindowManager繼承自ViewManager,這三個方法定義在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);
}

複製程式碼

可以看到這些方法傳入的引數是View,不是Window,說明WindowManager管理的是Window中的View,我們通過WindowManager操作Window就是在操作Window中的View。WindowManager的具體實現類是WindowManagerImp,我們看一下相應方法的實現,如下:

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;
    //...
    
    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }
    
      @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        //...
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        //...
        mGlobal.updateViewLayout(view, params);
    }
    
     @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
}
複製程式碼

可以看到WindowManagerImp也沒有做什麼,它把3個方法的操作都委託給了WindowManagerGlobal這個單例類,我們還看到了mParentWindow這個欄位,它是Window型別,是從構造中被傳入,所以WindowManager會持有Window的引用,這樣WindowManager就可以對Window做操作了。比如mGlobal.addView,我們可以理解為往window中新增View,在WindowManagerGlobal中,如下:

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow){
    //...
    ViewRootImpl root;
    root = new ViewRootImpl(view.getContext(), display);//註釋1
    //...
    root.setView(view, wparams, panelParentView);
}
複製程式碼

最終會走到ViewRootlmp的setView中, 如下:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  	//...	
    //這裡會進行View的繪製流程
    requestLayout();
     //...
    //通過session與WMS建立通訊
     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
    //...
}
複製程式碼

在ViewRootlmp的setView中,首先通過requestLayout()發起View繪製流程,然後在mWindowSession的addToDisplay中通過Binder與WMS進行跨程式通訊,請求顯示視窗上的檢視,至此View就會顯示到螢幕上。這個mWindowSession是一個IWindowSession.AIDL介面型別,用來實現跨程式通訊,在WMS內部會為每一個應用的請求保留一個單獨的Session,同樣實現了IWindowSession介面,應用與WMS之間的通訊就通過這個Session。那麼這個mWindowSession什麼時候被賦值的呢?就在上面的註釋1中,我們開啟ViewRootlmp的建構函式,如下:

public ViewRootImpl(Context context, Display display) {
    mWindowSession = WindowManagerGlobal.getWindowSession();
    //...
}
複製程式碼

可以看到mWindowSession是通過WindowManagerGlobal的單例類的getWindowSession()獲得的,我們開啟WindowManagerGlobal的getWindowSession(),如下:

 public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    //1、首先獲取WMS的本地代理
                    IWindowManager windowManager = getWindowManagerService();
                    //2、通過WMS的本地代理的openSession來獲取Session
                    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;
        }
    }
複製程式碼

我們首先看1,getWindowManagerService()原始碼如下:

public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                //獲取WMS的本地代理物件
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
              	//...
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }
複製程式碼

可以看到, ServiceManager.getService("window")就是獲得WMS,然後通過IWindowManager.Stub.asInterface()轉換成WMS在應用程式的本地代理,getWindowManagerService()就是返回WMS在本地應用程式的代理。(這裡涉及到Binder知識)

然後看2,通過WMS的本地代理的openSession來獲取Session,我們可以在WMS中找到這個函式實現,如下:

  @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
        //...
        //為每個視窗請求建立一個Session並返回
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }

複製程式碼

至此建立起與WMS的通訊的橋樑。然後WindowManager就間接的通過Session向WMS發起顯示視窗檢視的請求,WMS會嚮應用返回和視窗互動的資訊。至於mGlobal.updateViewLayout和mClobal.removeView也是類似的過程,可自行研究。

WindowManagerService

WindowManagerService是一個系統級服務,由SystemService啟動,實現了IWindowManager.AIDL介面,它的主要功能分為以下倆方面:

1、視窗管理

它負責視窗的啟動,新增和刪除,它還負責視窗的層級顯示(z-orderes)和維護視窗的狀態。我們繼續上面的mGlobal.addView,上面講到這個方法是向WMS發起一個顯示視窗檢視的請求,最終會走到mWindowSession.addToDisplay()方法,我們可以在Session中找到這個函式實現,如下:

 @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        //返回WMS中addWindow所返回的結果
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
複製程式碼

可以看到addToDisplay方法中最終返回了WMS中addWindow所返回的結果,Window的新增請求就交給WMS去處理,addWindow的實現在WMS中,裡面程式碼很長,這裡就不再深究了(留在下一篇文章從一個例子分析),addWindow主要做的事情是先進行視窗的許可權檢查,因為系統視窗需要宣告許可權,然後根據相關的Display資訊以及視窗資訊對視窗進行校對,再然後獲取對應的WindowToken,再根據不同的視窗型別檢查視窗的有效性,如果上面一系列步驟都通過了,就會為該視窗建立一個WindowState物件,以維護視窗的狀態和根據適當的時機調整視窗狀態,最後就會通過WindowState的attach方法與SurfaceFlinger通訊。因此SurfaceFlinger能使用這些Window資訊來合成surfaces,並渲染輸出到顯示裝置。

2、輸入事件的中轉站

當我們的觸控螢幕時就會產生輸入事件,在Android中負責管理事件的輸入是InputManagerService,在啟動IMS的時候會在native層建立NativeInputManager,在NativeInputManager的構造中會建立InputManager和Eventhub(監聽/dev/input/裝置節點中所有事件的輸入),在InputManager構造中會依此建立InputDispatcher、InputReader、InputReaderThread、InputDispatcherThread

InputReader執行在InputReaderThread中,它會不斷迴圈從EventHub(呼叫EventHub的getEvent()方法)中讀取原始輸入事件,InputReader將這些原始輸入事件加工後就交給執行在InputDispatcherThread中的InputDispatcher,而InputDispatcher它會尋找一個最合適的視窗來處理輸入事件,WMS是視窗的管理者,WMS會把所有視窗的資訊更新到InputDispatcher中,這樣InputDispatcher就可以將輸入事件派發給合適的Window,Window就會把這個輸入事件傳給頂級View,然後就會涉及我們熟悉的事件分發機制。

我們來再來看在ViewRootImp的setView中呼叫mWindowSession.addToDisplay方法時傳入的引數:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  	//...	
   	mInputChannel = new InputChannel();
    //...
    //通過session與WMS建立通訊,同時通過InputChannel接收輸入事件回撥
     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
    //...
     if (mInputChannel != null) {
         //...
         //處理輸入事件回撥
         mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
     }

}
複製程式碼

注意這個傳入的mInputChannel引數,它是InputChannel型別,它實現了Parcelable介面,用於接受WMS返回來的輸入事件,在WMS中會建立兩個InputChannel例項,一個會通過mInputChannel引數傳回來,一個會放在WMS的WindowState中,WindowState中的InputChannel會交給InputDispatcher,這樣應用端和InputDispatcher端就可以通過這兩個InputChannel來進行事件的接收和傳送。

它們之間的類圖關係如下:

Window, WindowManager和WindowManagerService之間的關係

總結

通過上面簡單的介紹,我們知道Window是View的載體,我們想要對Window進行刪除,新增,更新View就得通過WindowManager,WindowManager與WMS通過Session進行通訊,具體的實現就交給了WMS處理,WMS會為每一個Window建立一個WindowState並管理它們,具體的渲染工作WMS就交給SurfaceFinger處理。本文所討論的WMS系統相關結構如下:

Window, WindowManager和WindowManagerService之間的關係

參考資料:

《Android原始碼設計模式》

相關文章