WmS詳解(一)之token到底是什么?基於Android7.0源碼


做Android有些年頭了,Framework層三大核心View系統,WmS、AmS最近在研究中,這三大塊,每一塊都夠寫一個小冊子來介紹,其中View系統的介紹,我之前有一個系列的博客(不過由於時間原因,該系列尚未收尾,后續分析仍在探究中),小伙伴們自行查找。WmS和AmS這兩個也需要我們一個小塊一個小塊來啃,那么今天我們就先來看看WmS中涉及到的一個小小的變量token,這個東西到底是什么?

緣起

token這個東西有過幾年開發經驗的小伙伴應該都清楚,即使沒有認真研究過也至少遇到過,在我們使用PopupWindow的時候,這個里邊有一個方法是showAtLocation,該方法第一個參數是一個View,但是這個View是當前頁面的任意一個View,那么這個View是干什么用的呢?我們來看看這個方法的注釋:

/**
 * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
 * @param gravity the gravity which controls the placement of the popup window
 * @param x the popup's x location offset
 * @param y the popup's y location offset
 */
public void showAtLocation(View parent, int gravity, int x, int y) {
    showAtLocation(parent.getWindowToken(), gravity, x, y);
}

我這里只列出了一部分注釋,但是這一部分注釋說的很明白了,使用View這個參數的目的是為了獲取一個token。

OK,這個可能是很多小伙伴第一次間接用到token的情況,除了這里,在Android5.0中,有一個新控件,叫做SnackBar(android開發之SnackBar的使用),SnackBar在顯示的時候也需要一個當前頁面任意View,這里的目的和PopupWindow的原因類似,那么這個Token到底是什么?我們又為什么需要這樣一個東西,OK,繼續往下看。

上下求索

本篇博客實際上是為我后面全面介紹WmS做鋪墊,所以在這里我暫時先不想用過多篇幅去介紹Window,Window我打算放到后面再說。我們這里就直接先來看什么是token。單從字面來理解,token有令牌、符號的含義,當我嘗試去WmS中去尋找token變量的時候,在Android的framework層的好多個類中我都找到了,比如Activity中、Window中、WindowManager.LayoutParams中等等都有,我總結了如下一張表格:


我們看到,這么多類中都定義了token這個東東,而且這個token竟然是一個IBinder對象,有的類中雖然定義token時沒有直接指定為IBinder,但是追根溯源,發現其實最終說的還是IBinder,比如View.AttachInfo這個類中。其實當小伙伴們看到IBinder之后,應該立馬就想到了IPC,這是套路。我們知道,Android的framework框架在整個系統中扮演的角色相當於服務端,而我們開發的應用程序相當於客戶端,Activity的創建、啟動等操作都是通過IPC的方式來實現服務端和客戶端之間的通信,所以說IPC在這里扮演了相當重要的角色。OK,說完了這些,我們來分別看看幾個重要的token。

Activity中的token

要了解Activity中的token,我們得先明白Activity中的另外一個東西,叫做ActivityRecord,ActivityRecord是AmS中用來保存一個Activity信息的輔助類,這個類中有許多屬性,這些屬性可以從整體上分為兩大部分:Activity所處的環境信息和運行狀態信息。Activity所處的環境信息主要包括Activity所屬的Package(對應變量為packageName)、所在進程名稱(對應變量為processName)、圖標(對應變量icon)、主題(對應變量theme)等;運行狀態信息主要有idle、stop、finishing等,這些狀態信息與Activity生命周期相關。這里有一個地方需要小伙伴們注意,在ActivityRecord中有一個變量叫做appToken,這個變量的類型是一個IApplicationToken.Stub,這里的Token提供了對ActivityRecord類的基本操作,看到這里,小伙伴們應該心里有數了,這個appToken也是一個Binder,可以進行IPC調用,這里我們一般是在WmS中對該對象進行IPC調用。

OK,說完了ActivityRecord,我們再來看一看Activity中的mToken變量。這個變量的本質是一個Binder,通過查找源碼,我們發現,該變量的賦值是在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) {
		
		......
		......
		
        mToken = token;
		
		......
		......
    }

OK,研究過Activity啟動過程的小伙伴應該都清楚attach的調用位置吧,就是ActivityThread這個類啦,在ActivityThread的scheduleLaunchActivity方法中,我們拿到了參數token,這個token經過好幾道程序,最終變為了上文的token。由於AmS為每一個創建的Activity都創建了一個ActivityRecord,由於Binder可以用來標識多個進程間的同一個對象,所以這里的mToken變量還有一個作用就是指向了ActivityRecord。在我們的窗口創建過程中,涉及到的IPC通信就是兩方面:一個是指向某個W類的token,另一個是指向ActivityRecord的token,指向W類的token主要是用來實現WmS和應用所在進程通信,指向ActivityRecord的token則是實現WmS和AmS通信的。Activity中的mToken很明顯是第二種。

Window中的mAppToken

在我們的Android系統中,每一個Window對象都有一個mAppToken變量,但是小伙伴注意區分Window和窗口(窗口本質上就是一個View,而Window是一個應用窗口的抽象,WmS把所有的用戶消息發給View/ViewGroup,但是在View/ViewGroup處理消息的過程中,有一些操作是公共的,Window把這些公共行為抽象出來,是為Window)。由於窗口本質是一個View,和Window沒有關系,所以這里mAppToken並不是W類的引用,那不是W類的引用,就只能是ActivityRecord的引用了,既然是ActivityRecord的引用,那么毫無疑問,mAppToken的主要功能就是實現WmS和AmS之間的通信。但是在實際開發中,由於Window並不總是對應一個Activity,我們常見的Dialog,ContextMenu等等中也會包含一個Window,這個時候就不牽涉WmS和AmS之間的通信問題了,那么這個時候Window中的mAppToken為空,否則mAppToken和Activity中的mToken是相同的。

WindowManager.LayoutParams中的token

WindowManager.LayoutParams中竟然會有一個token,很多小伙伴可能會覺得奇怪,其實仔細想想這也沒什么,WindowManager類可以向WmS中添加一個窗口,窗口添加成功之后,我們還要和這個窗口進行通信,通信就需要Binder,也就是這里所說的token了。但是由於我們往WmS中添加的窗口類型可能會有差異,所以token的含義也會有差別。這里的差別主要體現在WindowManagerService的addWindow方法上,總結該方法,我們可以發現token的取值一共有三種情況:

1.如果我們創建的是一個應用窗口,比如Activity,那么這里的token的值和Window中的mAppToken的值相同,也就是和Activity的mToken的值相同,都是指向ActivityRecord對象,但是在調用addView方法的時候,系統會對這里的token的值進行調整,使之變為一個指向W的對象,這樣,WmS就可以通過這個token進行IPC調用,從而控制窗口的行為。

2.如果我們創建的窗口為子窗口,即Dialog、PopupWindow等,那么token就為其父窗口的W對象,如果查找不到父窗口,或者父窗口的類型還是子窗口,那么都會拋出異常。

3.如果創建的是系統窗口,那么分兩種情況,對於TYPE _ INPUT _ METHOD,TYPE _ VOICE _ INTERACTION,TYPE _ WALLPAPER,TYPE _ DREAM,TYPE _ ACCESSIBILITY _ OVERLAY這些系統窗口,token是不可以為null的,而對於其他的系統窗口,token可以為null,這里對應的源碼如下(由於這里源碼太長,我這里貼出一部分):

if (token == null) {
                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            }

View中的token

View中並不直接存在一個token,但是View中有一個mAttachInfo,這個變量中token,所以在這里我們需要先分析一下這個mAttachInfo到底是個什么東西?在View中和ViewRootImpl 中都有一個mAttachInfo,而一個應用中的每一個View都對應了一個ViewRootImpl對象,ViewRootImpl中有一個mAttachInfo對象,這個對象是在ViewRootImpl被構造的時候創建的。ViewRootImpl中的mAttachInfo對象的數據類型就是View.AttachInfo,所以說這兩個對象其實是同一種類型,但是到底是不是同一個東西,這個還需要我們進一步考究。當一個View在屏幕上顯示出來的時候,它必須經歷一個過程就是View的繪制,了解過View繪制過程的小伙伴應該都明白,View繪制有一個核心方法就是ViewRootImpl.performTraversals,在這個方法中,系統會去調用View/ViewGroup的dispatchAttachedToWindow方法,源碼如下:

host.dispatchAttachedToWindow(mAttachInfo, 0);

這個時候又回到了View方法,在View的dispatchAttachToWindow方法中,ViewRootImpl中的mAttachInfo竟然賦值給了View中的mAttachInfo了。也就是說,View中的mAttachInfo和ViewRootImpl中的mAttachInfo其實是同一個東西,mAttachInfo中有三個Binder變量,這個我們來看其中兩個:

mWindowToken,這個表示的是當前窗口所對應的W對象,因為View本身並不能直接從WmS中接收消息,要通過W類才能實現,故此mWindowToken指向了該窗口對應的W對象。

mPanelParentWindowToken,如果該窗口為子窗口,那么該變量就是父窗口中的W對象。


總結

OK,以上就是我們WmS系統中幾個常見的token,這些token基本都和Binder脫不了關系,Binder又是為了進行IPC調用,WmS中的IPC調用大致又可以分為兩類,所以總結起來就是這樣:窗口的創建一般會涉及到兩方面的IPC通信,一個是WmS和應用所在進程進行通信,還有一個就是WmS和AmS進行通信。就是這兩種,第一種對應的token指向一個ViewRootImpl.W對象,第二種對應的token指向一個ActivityRecord對象。


最后再說說開篇說的PopupWindow中的問題,很明顯PopupWindow中的第一個參數是為了獲取View中的mAttachInfo,進而獲取mAttachInfo中的指向W類的Binder對象,然后通過該對象就能獲取用戶的輸入了。


OK,就這些吧,有問題歡迎留言討論。


參考資料:

1.Android Binder機制(一) Binder的設計和框架

2.Binder系列—開篇

3.淺析Android的窗口



以上。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM