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的窗口
轉載于:https://www.cnblogs.com/lenve/p/7530995.html
總結
以上是生活随笔為你收集整理的WmS详解(一)之token到底是什么?基于Android7.0源码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: easyui treegrid idFi
- 下一篇: noip2016考前模板