深入理解窗口令牌WindowToken
1.WindowToken的意義
為了搞清楚WindowToken的作用是什么,看一下其位于WindowToken.java中的定義。雖然它沒有定義任何函數(shù),但其成員變量的意義卻很重要。
- WindowToken將屬于同一個應用組件的窗口組織在了一起。所謂的應用組件可以是Activity、InputMethod、Wallpaper以及Dream。在WMS對窗口的管理過程中,用WindowToken指代一個應用組件。例如在進行窗口ZOrder排序時,屬于同一個WindowToken的窗口會被安排在一起,而且在其中定義的一些屬性將會影響所有屬于此WindowToken的窗口。這些都表明了屬于同一個WindowToken的窗口之間的緊密聯(lián)系。
- WindowToken具有令牌的作用,是對應用組件的行為進行規(guī)范管理的一個手段。WindowToken由應用組件或其管理者負責向WMS聲明并持有。應用組件在需要新的窗口時,必須提供WindowToken以表明自己的身份,并且窗口的類型必須與所持有的WindowToken的類型一致。從上面的代碼可以看到,在創(chuàng)建系統(tǒng)類型的窗口時不需要提供一個有效的Token,WMS會隱式地為其聲明一個WindowToken,看起來誰都可以添加個系統(tǒng)級的窗口。難道Android為了內(nèi)部使用方便而置安全于不顧嗎?非也,addWindow()函數(shù)一開始的mPolicy.checkAddPermission()的目的就是如此。它要求客戶端必須擁有SYSTEM_ALERT_WINDOW或INTERNAL_SYSTEM_WINDOW權(quán)限才能創(chuàng)建系統(tǒng)類型的窗口。
2.向WMS聲明WindowToken
既然應用組件在創(chuàng)建一個窗口時必須指定一個有效的WindowToken才行,那么WindowToken究竟該如何聲明呢?
在SampleWindow應用中,使用wms.addWindowToken()函數(shù)聲明mToken作為它的令牌,所以在添加窗口時,通過設(shè)置lp.token為mToken向WMS進行出示,從而獲得WMS添加窗口的許可。這說明,只要是一個Binder對象(隨便一個),都可以作為Token向WMS進行聲明。對于WMS的客戶端來說,Token僅僅是一個Binder對象而已。
為了驗證這一點,來看一下addWindowToken的代碼,如下所示:
WindowManagerService.java::WindowManagerService.addWindowToken()
@Override publicvoid addWindowToken(IBinder token, int type) { // 需要聲明Token的調(diào)用者擁有MANAGE_APP_TOKENS的權(quán)限 if(!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addWindowToken()")) { thrownew SecurityException("Requires MANAGE_APP_TOKENS permission"); } synchronized(mWindowMap){ ...... // 注意其構(gòu)造函數(shù)的參數(shù)與addWindow()中不同,最后一個參數(shù)為true,表明這個Token // 是顯式申明的 wtoken= new WindowToken(this, token, type, true); mTokenMap.put(token,wtoken); ...... } }
使用addWindowToken()函數(shù)聲明Token,將會在WMS中創(chuàng)建一個WindowToken實例,并添加到mTokenMap中,鍵值為客戶端用于聲明Token的Binder實例。與addWindow()函數(shù)中隱式地創(chuàng)建WindowToken不同,這里的WindowToken被聲明為顯式的。隱式與顯式的區(qū)別在于,當隱式創(chuàng)建的WindowToken的最后一個窗口被移除后,此WindowToken會被一并從mTokenMap中移除。顯式創(chuàng)建的WindowToken只能通過removeWindowToken()顯式地移除。
addWindowToken()這個函數(shù)告訴我們,WindowToken其實有兩層含義:
- 對于顯示組件(客戶端)而言的Token,是任意一個Binder的實例,對顯示組件(客戶端)來說僅僅是一個創(chuàng)建窗口的令牌,沒有其他的含義。
- 對于WMS而言的WindowToken這是一個WindowToken類的實例,保存了對應于客戶端一側(cè)的Token(Binder實例),并以這個Token為鍵,存儲于mTokenMap中??蛻舳艘粋?cè)的Token是否已被聲明,取決于其對應的WindowToken是否位于mTokenMap中。
注意 在一般情況下,稱顯示組件(客戶端)一側(cè)Binder的實例為Token,而稱WMS一側(cè)的WindowToken對象為WindowToken。但是為了敘述方便,在沒有歧義的前提下不會過分仔細地區(qū)分這兩個概念。
接下來,看一下各種顯示組件是如何聲明WindowToken的。
(1) Wallpaper和InputMethod的Token
Wallpaper的Token聲明在WallpaperManagerService中。參考以下代碼:
WallpaperManagerService.java::WallpaperManagerService.bindWallpaperComponentLocked()
BooleanbindWallpaperComponentLocked(......) { ...... WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper); ...... mIWindowManager.addWindowToken(newConn.mToken, WindowManager.LayoutParams.TYPE_WALLPAPER); ...... }
WallpaperManagerService是Wallpaper管理器,它負責維護系統(tǒng)已安裝的所有的Wallpaper并在它們之間進行切換,而這個函數(shù)的目的是準備顯示一個Wallpaper。newConn.mToken與SampleWindow例子一樣,是一個簡單的Binder對象。這個Token將在即將顯示出來的Wallpaper被連接時傳遞給它,之后Wallpaper即可通過這個Token向WMS申請創(chuàng)建繪制壁紙所需的窗口了。
注意 :WallpaperManagerService向WMS聲明的Token類型為TYPE_WALLPAPER,所以,Wallpaper僅能本分地創(chuàng)建TYPE_WALLPAPER類型的窗口。
相應的,WallpaperManagerService會在detachWallpaperLocked()函數(shù)中取消對Token的聲明:
WallpaperManagerService.java::WallpaperManagerService.detachWallpaperLocked()
booleandetachWallpaperLocked(WallpaperData wallpaper){ ...... mIWindowManager.removeWindowToken(wallpaper.connection.mToken); ...... }
再此之后,如果這個被detach的Wallpaper想再要創(chuàng)建窗口便不再可能了。
WallpaperManagerService使用WindowToken對一個特定的Wallpaper做出了如下限制:
- Wallpaper只能創(chuàng)建TYPE_WALLPAPER類型的窗口。
- Wallpaper顯示的生命周期由WallpaperManagerService牢牢地控制著。僅有當前的Wallpaper才能創(chuàng)建窗口并顯示內(nèi)容。其他的Wallpaper由于沒有有效的Token,而無法創(chuàng)建窗口。
InputMethod的Token的來源與Wallpaper類似,其聲明位于InputMethodManagerService的startInputInnerLocked()函數(shù)中,取消聲明的位置在InputmethodManagerService的unbindCurrentMethodLocked()函數(shù)。InputMethodManagerService通過Token限制著每一個InputMethod的窗口類型以及顯示生命周期。
(2) Activity的Token
Activity的Token的使用方式與Wallpaper和InputMethod類似,但是其包含更多的內(nèi)容。畢竟,對于Activity,無論是其組成還是操作都比Wallpaper以及InputMethod復雜得多。對此,WMS專為Activity實現(xiàn)了一個WindowToken的子類:AppWindowToken。
既然AppWindowToken是為Activity服務的,那么其聲明自然在ActivityManagerService中。具體位置為ActivityStack.startActivityLocked(),也就是啟動Activity的時候。相關(guān)代碼如下:
ActivityStack.java::ActivityStack.startActivityLocked()
private final void startActivityLocked(......) { ...... mService.mWindowManager.addAppToken(addPos,r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen); ...... }
startActivityLocked()向WMS聲明r.appToken作為此Activity的Token,這個Token是在ActivityRecord的構(gòu)造函數(shù)中創(chuàng)建的。隨然后在realStartActivityLocked()中將此Token交付給即將啟動的Activity。
ActivityStack.java::ActivityStack.realStartActivityLocked()
final boolean realStartActivityLocked(......) { ...... app.thread.scheduleLaunchActivity(newIntent(r.intent), **r.appToken,** System.identityHashCode(r), r.info, newConfiguration(mService.mConfiguration), r.compat, r.icicle, results, newIntents,!andResume, mService.isNextTransitionForward(),profileFile, profileFd, profileAutoStop); ...... }
啟動后的Activity即可使用此Token創(chuàng)建類型為TYPE_APPLICATION的窗口了。
取消Token的聲明則位于ActivityStack.removeActivityFromHistoryLocked()函數(shù)中。
Activity的Token在客戶端是否和Wallpaper一樣,僅僅是一個基本的Binder實例呢?其實不然??匆幌聄.appToken的定義可以發(fā)現(xiàn),這個Token的類型是IApplicationToken.Stub。其中定義了一系列和窗口相關(guān)的一些通知回調(diào),它們是:
- windowsDrawn(),當窗口完成初次繪制后通知AMS。
- windowsVisible(),當窗口可見時通知AMS。
- windowsGone(),當窗口不可見時通知AMS。
- keyDispatchingTimeout(),窗口沒能按時完成輸入事件的處理。這個回調(diào)將會導致ANR。
- getKeyDispatchingTimeout(),從AMS處獲取界定ANR的時間。
AMS通過ActivityRecord表示一個Activity。而ActivityRecord的appToken在其構(gòu)造函數(shù)中被創(chuàng)建,所以每個ActivityRecord擁有其各自的appToken。而WMS接受AMS對Token的聲明,并為appToken創(chuàng)建了唯一的一個AppWindowToken。因此,這個類型為IApplicationToken的Binder對象appToken粘結(jié)了AMS的ActivityRecord與WMS的AppWindowToken,只要給定一個ActivityRecord,都可以通過appToken在WMS中找到一個對應的AppWindowToken,從而使得AMS擁有了操縱Activity的窗口繪制的能力。例如,當AMS認為一個Activity需要被隱藏時,以Activity對應的ActivityRecord所擁有的appToken作為參數(shù)調(diào)用WMS的setAppVisibility()函數(shù)。此函數(shù)通過appToken找到其對應的AppWindowToken,然后將屬于這個Token的所有窗口隱藏。
注意: 每當AMS因為某些原因(如啟動/結(jié)束一個Activity,或?qū)ask移到前臺或后臺)而調(diào)整ActivityRecord在mHistory中的順序時,都會調(diào)用WMS相關(guān)的接口移動AppWindowToken在mAppTokens中的順序,以保證兩者的順序一致。在后面講解窗口排序規(guī)則時會介紹到,AppWindowToken的順序?qū)Υ翱诘捻樞蛴绊懛浅4蟆?/p>
到此這篇關(guān)于深入理解窗口令牌WindowToken的文章就介紹到這了,更多相關(guān)窗口令牌WindowToken內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java數(shù)組,去掉重復值、增加、刪除數(shù)組元素的方法
下面小編就為大家?guī)硪黄狫ava數(shù)組,去掉重復值、增加、刪除數(shù)組元素的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10使用springboot通過spi機制加載mysql驅(qū)動的過程
這篇文章主要介紹了使用springboot通過spi機制加載mysql驅(qū)動的過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07