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