Window表示一個窗口的概念,在日常開發(fā)中直接接觸Window的機(jī)會并不多前痘,但是在某些特殊時候我們需要在桌面上顯示一個類似懸浮窗的東西,那么這種效果就需要用到Window來實現(xiàn)。Window是一個抽象類梦皮,它的具體實現(xiàn)是PhoneWindow。創(chuàng)建一個Window是很簡單的事桃焕,只需要通過WindowManager即可完成剑肯。WindowManager是外界訪問Window的入口,Window的具體實現(xiàn)位于WindowManagerService中覆旭,WindowManager和WindowManagerService的交互是一個IPC過程退子。Android中所有的視圖都是通過Window來呈現(xiàn)的,不管是Activity,Dialog還是Toast型将,他們的視圖實際上都是附加在Window上的寂祥,因此Window實際是View的直接管理者。View的事件分發(fā)機(jī)制也可以知道七兜,單擊事件由Window傳遞給DecorView丸凭,然后再由DecorView傳遞給我們的View,就連Activity的設(shè)置視圖的方法setContentView在底層也是通過Window來完成的。
WindowManager.LayoutParams中的flags和type這兩個參數(shù)比較重要惜犀。
Flags參數(shù)表示W(wǎng)indow的屬性铛碑,它有很多選項,通過這些選項可以控制Window的顯示特性虽界。
FLAG_NOT_FOCUSABLE:表示W(wǎng)indow不需要獲取焦點汽烦,也不需要接收各種輸入事件。此標(biāo)記會同時啟用FLAG_NOT_TOUCH_MODAL最終事件會直接傳遞給下層的具有焦點的Window莉御。
FLAG_NOT_TOUCH_MODAL:在此模式下撇吞,系統(tǒng)會將當(dāng)前Window區(qū)域以外的單擊事件傳遞給底層的Window,當(dāng)前Window區(qū)域以內(nèi)的單擊事件則自己處理礁叔。這個標(biāo)記很重要牍颈,一般來說都需要開啟此標(biāo)記,否則其他Window將無法接收到單擊事件琅关。
FLAG_SHOW_WHEN_LOCKED:開啟此模式可以讓W(xué)indow顯示在鎖屏的界面上煮岁。
Type參數(shù)表示W(wǎng)indow的類型,Window有三種類型涣易,分別是應(yīng)用Window画机,子Window和系統(tǒng)Window。應(yīng)用類Window對應(yīng)著一個Activity新症。子Window不能單獨存在色罚,它需要附屬在特定的父Window之中,比如常見的一些Dialog就是一個子Window账劲。系統(tǒng)Window是需要聲明權(quán)限在能創(chuàng)建的Window戳护,比如Toast和系統(tǒng)狀態(tài)欄這些都是系統(tǒng)Window。
Window是分層的瀑焦,每個Window都有對應(yīng)的z-ordered,層級大的會覆蓋在層級小的Window的上面腌且,這和HTML中的z-index的概念是完全一致的。在三類Window中榛瓮,應(yīng)用Window的層級范圍是1-99铺董,子Window的層級范圍是1000-1999,系統(tǒng)Window的層級范圍是2000-2999禀晓,這些層級范圍對應(yīng)著WindowManager.LayoutParams的type參數(shù)精续。如果想要Window位于所有Window的最頂層,那么采用較大的層級即可粹懒。很顯然系統(tǒng)Window的層級是最大的重付。因為系統(tǒng)類型的Window是需要檢查權(quán)限的,如果不在AndroidManifest中使用相應(yīng)的權(quán)限凫乖,那么創(chuàng)建Window的時候就會報錯确垫。
WindowManager所提供的功能很簡單弓颈,常用的只有三個方法,即添加View删掀,更新View和刪除View翔冀,這三個方法定義在ViewManager中,而WindowManager繼承了ViewManager披泪。
對開發(fā)者來說纤子,WindowManager常用的就只有這三個功能而已,但是這三個功能已經(jīng)足夠我們使用了款票。它可以創(chuàng)建一個Window并向其添加View计福,還可以更新Window中的View,另外如果想要刪除一個Window徽职,那么只需要刪除它里面的View即可。WindowManager操作Window的過程更像是在操作Window中的View佩厚。常見的那種可以拖動的Window效果姆钉,其實是很好實現(xiàn)的,只需要根據(jù)手指的位置來設(shè)定LayoutParams中的x和y的值即可改變Window的位置抄瓦。首先給View設(shè)置onTouchListener潮瓶。mFloatingButton.setOnTouchListener(this)然后再onTouch方法中不斷更新View的位置即可。
Window的內(nèi)部機(jī)制
Window是一個抽象的概念钙姊,每一個Window都對應(yīng)著一個View和一個ViewRootImpl毯辅,Window和View通過ViewRootImpl來建立聯(lián)系,因此Window并不是實際存在的煞额,它是以View的形式存在思恐。這點從WindowManager的定義也可以看出,它提供的三個接口方法addView膊毁,updateViewLayout以及removeView都是針對View的胀莹,這說明View才是Window存在的實體。在實際使用中無法直接訪問Window婚温,對Window的訪問必須通過WindowManager描焰。
Window的添加過程
Window的添加過程需要通過WindowManager的addView來實現(xiàn),WindowManager是一個接口栅螟,它的真正實現(xiàn)是WindowMangerImpl類荆秦。WindowManagerImpl并沒有直接實現(xiàn)Window的三大操作,而是全部交給了WindowManagerGlobal來處理力图,WindowManagerGlobal以工廠的形式向外提供自己的實例步绸,在在WindowManagerGlobal中有如下一段代碼:private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance()。WindowManagerImp這種共工作模式是典型的橋接模式吃媒,將所有的操作全部委托給WindowManagerGlobal來實現(xiàn)靡努。WindowManagerGlobal的addView方法主要分為以下幾步:
1.檢查參數(shù)是否合法坪圾,如果是子Window那么還需要調(diào)整一些布局參數(shù)
2.創(chuàng)建ViewRootImpl并將View添加到列表中:WindowManagerGlobal內(nèi)部有幾個列表比較重要,mViews存儲的是所有Window所對應(yīng)的View惑朦,mRoots存儲的是所有Window所對應(yīng)的ViewRootImpl,mParams存儲的是所有Window所對應(yīng)的布局參數(shù)兽泄,而mDyingViews則存儲了那些正在被刪除的View對象,或者說是那些已經(jīng)調(diào)用removeView方法但是刪除操作還未完成的Window對象漾月。
3.通過ViewRootImpl來更新界面并完成Window的添加過程
Window的刪除過程
Window的刪除過程和添加過程一樣病梢,都是先通過WindowManagerImpl后,再進(jìn)一步通過WindowManagerGlobal來實現(xiàn)的梁肿。
如果是異步刪除蜓陌,那么就返送一個MSG_DIE的消息,ViewRootImp中的Handler會處理此消息并調(diào)用doDie方法吩蔑,如果是同步刪除(立即刪除)那么就不發(fā)消息直接調(diào)用doDie方法钮热,這就是這兩種刪除方式的區(qū)別。在doDie內(nèi)部會調(diào)用dispatchDetachedFromWindow方法烛芬,真正刪除View的邏輯在dispathcDetachedFromWindow方法的內(nèi)部實現(xiàn)隧期,dispatchDeatachedFromWindow方法主要做四件事:
1.垃圾回收相關(guān)的工作,比如清除數(shù)據(jù)和消息赘娄,移除回調(diào)仆潮。
2.通過Session的remove方法刪除Window:mWindowSession.remove(mWindow),這同樣是一個IPC過程,最終會調(diào)用WindowManagerService的removeWindow方法遣臼。
3.調(diào)用View的dispathcDetachedFromWindow方法性置,在內(nèi)部會調(diào)用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。對于onDetachedFromWindow()大家一定不陌生揍堰,當(dāng)View從Window中移除時鹏浅,這個方法就會被調(diào)用,可以在這個方法內(nèi)部做一些資源回收的工作屏歹,比如終止動畫篡石,停止線程等。
4.調(diào)用WindowManagerGlobal的doRemoveView方法刷新數(shù)據(jù)西采,包括mRoots凰萨,mParams以及mDyingViews,需要將當(dāng)前Window所關(guān)聯(lián)的這三類對象從列表中刪除械馆。
Window的更新過程
updateViewLayout方法做的事情就比較簡單了胖眷,首先它需要更新View的LayoutParams并替換掉老的LayoutParams,接著再更新ViewRootImpl中的LayoutParams這一步是通過ViewRootImpl的setLayoutParams方法來實現(xiàn)的霹崎。在ViewRootImpl中會通過scheduleTraversals方法來對View重新布局珊搀,包括測量,布局尾菇,重繪這三個過程境析,除了View本身的重繪以外囚枪,ViewRootImpl還會通過WindowSession來更新Window的視圖,這個過程最終是由WindowManagerService的relayoutWindow()來具體實現(xiàn)的劳淆,同樣是一個IPC過程链沼。
Window的創(chuàng)建過程
View是Android中的視圖的呈現(xiàn)方式,但是View不能單獨存在沛鸵,他必須附著在Window這個抽象的概念上面括勺,因此有視圖的地方就有Window。那些地方有視圖呢曲掰?這個讀者都比較清楚疾捍,Android中可以提供視圖的地方有Activity,Dialog,Toast除此之外栏妖,還有一些依托Window而實現(xiàn)的視圖乱豆,比如PopUpWindow,菜單吊趾,它們也是視圖宛裕,有視圖的地方就有Window,因此Activity,Dialog,Toast等視圖都對應(yīng)著一個Window。
Activity的Window創(chuàng)建過程
Actiity的啟動過程很復(fù)雜趾徽,最終會由ActivityThread中的performLaunchActivity()來完成整個啟動過程。在這個方法內(nèi)部會通過類加載器創(chuàng)建Activity的實例對象翰守,并調(diào)用其attach方法為其關(guān)聯(lián)運行過程中所依賴的一系列上下文環(huán)境變量孵奶。
在Activity的attach方法里,系統(tǒng)會創(chuàng)建Activity所屬的Window對象并為其設(shè)置回調(diào)接口蜡峰,Window對象的創(chuàng)建是通過PolicyManager的makeNewWindow方法實現(xiàn)的了袁。由于Activity實現(xiàn)了Window的Callback接口,因此當(dāng)Window接收到外界的狀態(tài)改變時就會回調(diào)Activity的方法湿颅。
Activity的Window是通過PolicyManager是一個工廠方法來創(chuàng)建的载绿,PolicyManager是一個策略類,其中實現(xiàn)的幾個工廠方法全部在策略接口Ipolicy中聲明了油航。PolicyManager的真正實現(xiàn)是Policy類崭庸,Policy類中的makeNewWindow方法如下,可以發(fā)現(xiàn)Window的具體實現(xiàn)的確是PhoneWindow谊囚。
public Window makeNewWindow(Context context){
? ? return new PhoneWindow(context);
}
Activity將具體實現(xiàn)交給了Window處理怕享,而Window的具體實現(xiàn)是PhoneWindow,所以只需要看PhoneWindow的相關(guān)邏輯即可镰踏。PhoneWindow的setContentView方法如下幾步:
1.如果沒有DecorView函筋,那么就創(chuàng)建它
DecorView是一個FrameLayout,DecorView是Acitivty中的頂級View奠伪,一般來說它的內(nèi)部包含標(biāo)題欄和內(nèi)部欄跌帐,但是這個會隨著主題的變換而發(fā)生改變首懈。不管怎么樣,內(nèi)容欄是一定要存在的谨敛,并且內(nèi)容欄具體固定的id究履,那就是“content”,它的完整id是android.R.id.content佣盒。DecorView的創(chuàng)建過程由installDecor方法來完成挎袜,在方法內(nèi)部會通過generateDecor方法來直接創(chuàng)建DecorView,這個時候DecorView還知識一個空白的FrameLayout肥惭。
2.將View添加到DecorView的mContentParent中
3.回調(diào)Activity的onContentChanged方法通知Activity視圖已經(jīng)發(fā)生改變盯仪。
經(jīng)過上面的三個步驟,到這里為止DecorView已經(jīng)被創(chuàng)建并初始化完畢蜜葱,Activity的布局文件也已經(jīng)成功添加到了DecorView的mContentParent中全景,但是這個時候DecorView還沒有被WindowManager正式添加到Window中。這里需要正確理解Window的概念牵囤,Window更多表示的是一種抽象的功能集合爸黄,雖然說早在Activity的attach方法中Window就已經(jīng)被創(chuàng)建了,但是這個時候由于DecorView并沒有被WindowManager識別揭鳞,所以這個時候的Window無法提供具體功能炕贵,因為它還無法接受外界的輸入信息。在AcitivityThread的handleResumeActivity方法中野崇,首先會調(diào)用Activity的onResume方法称开,接著會調(diào)用Activity的makeVisible(),正是在makeVisible方法中,DecorView真正地完成了添加和顯示這兩個過程乓梨,到這里Activity的視圖才能被用戶看到鳖轰。
Dialog的Window創(chuàng)建過程
Dialog的Window的創(chuàng)建過程和Activity類似,有如下幾步
1.創(chuàng)建Window
Dialog的Window的創(chuàng)建同樣是通過PolicyManager的makeNewWindow方法來完成的扶镀,創(chuàng)建后的對象實際上就是PhoneWindow蕴侣,這個過程和Activity的Window的創(chuàng)建過程是一致的。
2.初始化DecorView并將Dialog的視圖添加到DecorView中
3.將DecorView添加到Window中并顯示
Dialog的Window創(chuàng)建和Activity的Window創(chuàng)建過程很類似臭觉,二者幾乎沒有什么區(qū)別昆雀。當(dāng)Dialog被關(guān)閉時,它會通過WindowManager來移除DecorView:mWindowManager.removeViewImmediate(mDecor)蝠筑。普通的Dialog有一個特殊之處忆肾,那就是必須采用Activity的Context,如果采用Application的Context菱肖,那么就會報錯客冈。
應(yīng)用token一般只有Activity擁有,所以這里只需要用Activity作為Context來顯示對話框即可稳强。系統(tǒng)Window比較特殊场仲,它可以不需要token和悦。系統(tǒng)Window的層級有很多值,可以選用TYPE_SYSTEM_OVERLAY來指定對話框的Window類型為系統(tǒng)Window.不要忘記在AndroidManifest文件中聲明權(quán)限從而可以使用系統(tǒng)Window.
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW">
Toast的Window創(chuàng)建過程
Toast和Dialog不同渠缕,它的工作過程稍顯復(fù)雜鸽素,首先Toast也是基于Window來實現(xiàn)的,但是由于Toast具有定時取消這一功能亦鳞,所以系統(tǒng)采用了Handler馍忽。在Toast的內(nèi)部有兩類IPC過程,第一類是Toast訪問NotificationManagerService燕差,第二類是NotificationManagerService回調(diào)Toast里的TN接口遭笋。
Toast屬于系統(tǒng)Window,它內(nèi)部的視圖由兩種方式指定徒探,一種是系統(tǒng)默認(rèn)的樣式瓦呼,另一種是通過setView方法來指定一個自定義View,不管如何测暗,它們都對應(yīng)Toast的一個View類型的內(nèi)部成員mNextView央串。Toast提供了show和cancel分別用于顯示和隱藏Toast,它們的內(nèi)部是一個IPC過程碗啄。顯示和隱藏Toast都需要通過NMS來實現(xiàn)质和,由于NMS運行在系統(tǒng)的進(jìn)程中,所以只能通過遠(yuǎn)程調(diào)用的方式來顯示和隱藏Toast稚字。需要注意的是TN這個類饲宿,它是一個Binder類,在Toast和NMS進(jìn)行IPC的過程中尉共,當(dāng)NMS處理Toast的顯示或隱藏請求時會跨進(jìn)程回調(diào)TN中的方法褒傅,這個時候由于TN運行在Binder線程池中弃锐,所以需要通過Handler將其切換到當(dāng)前線程中袄友。這里的當(dāng)前線程是指發(fā)送Toast請求所在的線程,注意霹菊,由于這里使用了Handler剧蚣,所以這意味著Toast無法在沒有Looper的線程中彈出,這是因為Handler需要使用Looper才能完成切換線程的功能旋廷。
Toast的顯示過程調(diào)用了NMS中的enqueueToast方法鸠按,NMS的enqueueToast方法的第一個參數(shù)表示當(dāng)前應(yīng)用的包名,第二個參數(shù)tn表示遠(yuǎn)程回調(diào)饶碘,第三個參數(shù)表示Toast的時長目尖。enqueueToast首先將Toast請求封裝為ToastRecord對象并將其添加到一個名為mToastQueue的隊列中。mToastQueue其實是一個ArrayList扎运。對于非系統(tǒng)應(yīng)用來說瑟曲,mToastQueue中最多能同時存在50個ToastRecord饮戳,這樣做是為了防止DOS(Denial of Service)。如果不這么做洞拨,試想一下扯罐,如果我們通過大量的循環(huán)去連續(xù)彈出Toast,這將會導(dǎo)致其他應(yīng)用沒有機(jī)會彈出Toast烦衣,那么對于其他應(yīng)用的Toast請求歹河,系統(tǒng)的行為就是拒絕服務(wù),這就是拒絕服務(wù)攻擊的含義花吟,這種手段常用于網(wǎng)絡(luò)攻擊中秸歧。
正常情況下,一個應(yīng)用不可能達(dá)到上限示辈,當(dāng)ToastRecord被添加到mToastQueue中后寥茫,NMS就會通過showNextToastLocked方法來顯示當(dāng)前的Toast。Toast的顯示是由ToastRecord的callback來完成的矾麻,這個callback實際上就是Toast中的TN對象的遠(yuǎn)程Binder纱耻,通過callback來訪問TN中的方法是需要跨進(jìn)程來完成的,最終被調(diào)用的TN中的方法會運行在發(fā)起Toast請求的應(yīng)用的Binder線程池中险耀。
Toast顯示以后弄喘,NMS還會通過scheduleTimeoutLocked方法來發(fā)送一個延時消息,具體的延時取決于Toast的時長甩牺。LONG_DELAY是3.5s蘑志,SHORT_DELAY是2s。延遲相應(yīng)的時間后贬派,NMS會通過cancelToastLocked方法來隱藏Toast并將其從mToastQueue中移除急但,這個時候如果mToastQueue中還有其他Toast,那么NMS就繼續(xù)顯示其他Toast搞乏。
Toast的隱藏也是通過ToastRecord的callback來完成的波桩,這同樣也是一次IPC過程。Toast的顯示和影響過程實際上是通過Toast中的TN這個類來實現(xiàn)的请敦,它有兩個方法show和hide镐躲,分別對應(yīng)Toast的顯示和隱藏。由于這兩個方法是被NMS以跨進(jìn)程的方式調(diào)用的侍筛,因此它們運行在Binder線程池中萤皂,為了將執(zhí)行環(huán)境切換到Toast請求所在的線程,在它們的內(nèi)部使用了Handler匣椰。
show和hide方法中的mShow和mHide是兩個Runnable裆熙,它們內(nèi)部分別調(diào)用了handleShow和handleHide方法。由此可見,handleShow和handleHide才是真正完成顯示和隱藏Toast的地方入录。TN的handlerShow中會將Toast的視圖添加到Window中齐媒。 而NT的handleHide中會將Toast的視圖從Window中移除。到這里Toast的Window的創(chuàng)建過程已經(jīng)分析完了纷跛。