Window是一個抽象類,它的具體實現(xiàn)是PhoneWindow。WindowManager是外界訪問Window的入口户辱,Window的具體實現(xiàn)位于WindowManagerService中,WindowManager和WindowManagerService的交互是一個IPC過程。Android中所有的視圖都是通過Window來呈現(xiàn)的才顿,不管是Activity返奉、Dialog還是Toast,它們的視圖實際上都是附加在Window上的必逆,因此Window實際是View的直接管理者怠堪。
Window和WindowManager
為了分析Window的工作機制,先通過代碼了解如何使用WindowManager添加一個Window末患,下面一段代碼將一個Button添加到屏幕坐標為(100, 300)的位置上
mFloatingButton = new Button(this);
mFloatingButton.setText("test button");
mLayoutParams = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,
PixelFormat.TRANSPARENT);//0,0 分別是type和flags參數(shù)研叫,在后面分別配置了
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mFloatingButton.setOnTouchListener(this);
mWindowManager.addView(mFloatingButton, mLayoutParams);
Flags參數(shù)表示W(wǎng)indow的屬性,以下列舉常用的選項:
- FLAG_NOT_FOCUSABLE:表示W(wǎng)indow不需要獲取焦點璧针,也不需要接收各種輸入事件嚷炉,此標記會同時啟動FLAG_NOT_TOUCH_MODEL,最終事件會傳遞給下層的具有焦點的Window
- FLAG_NOT_TOUCH_MODAL:在此模式下探橱,系統(tǒng)會將當前Window區(qū)域以外的單擊事件傳遞給底層的Window申屹,當前Window區(qū)域以內(nèi)的單擊事件則自己處理。這個標記很重要隧膏,一般來說都需要開啟此標記哗讥,否則其他Window將無法收到單擊事件。
- FLAG_SHOW_WHEN_LOCKED:開啟此模式可以讓顯示在鎖屏的界面
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的層級范圍是199逆趋,子Window的層級范圍是10001999盏阶,系統(tǒng)Window的層級范圍是2000~2999,這些層級屬性范圍對應(yīng)著WindowManager.LayoutParams的type參數(shù)闻书。
如果采用TYPE_SYSTEM_ERROR名斟,只需要為type參數(shù)指定這個層級即可:
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR
同時聲明權(quán)限:
<uses-permissionandroid:name="android.permission.SYSTEM_ALERT_WINDOW" />
WindowManager所提供的功能很簡單脑慧,常用的只有三個方法,即添加View砰盐、更新View和刪除View闷袒,這三個方法定義在ViewManager中,而WindowManager繼承了ViewManager岩梳。
Window的內(nèi)部機制
Window是一個抽象的概念囊骤,并不是實際存在的,它是以View的形式存在冀值,每一個Window都對應(yīng)著一個View和一個ViewRootImpl也物,Window和View通過ViewRootImpl來建立聯(lián)系。在實際使用中無法直接訪問Window列疗,對Window的訪問必須通過WindowManager滑蚯。
Window的添加過程
Window的添加過程需要通過WindowManager的addView來實現(xiàn),WindowManager是一個接口抵栈,它的真正實現(xiàn)是WindowManagerImpl類告材。WindowManager的實現(xiàn)類對于addView、updateView和removeView方法都是委托給WindowManagerGlobal類古劲。
WindowManagerImpl.calss
被標記為@hide
類斥赋,源碼在sdk/sources/android-22/android/view目錄下;
WindowManagerGlobal.calss
被標記為@hide
類产艾,源碼在sdk/sources/android-22/android/view目錄下疤剑;
ViewRootImpl.calss
被標記為@hide
類,源碼在sdk/sources/android-22/android/view目錄下闷堡;
PhoneWindow.calss
骚露,源碼在sdk/sources/android-22/com/android/internal/policy/impl目錄下;
WindowManagerGlobal的addView方法分為如下幾步:
- 檢查參數(shù)是否合法缚窿,如果是子Window那么還需要調(diào)整一些布局參數(shù)
- 創(chuàng)建ViewRootImpl并將View添加到列表中
- 通過ViewRootImpl來更新界面并完成Window的添加過程。ViewRootImpl內(nèi)部通過WindowSession的實現(xiàn)類Session來調(diào)用WindowManagerService的方法來實現(xiàn)addWindow焰扳。
Window的刪除過程
和添加過程一樣倦零,都是先通過WindowManagerImpl后,再進一步通過WindowManagerGlobal來實現(xiàn)的吨悍。
真正刪除View的邏輯在dispatchDetachedFromWindow方法的內(nèi)部實現(xiàn)扫茅。dispatchDetachedFromWindow方法主要做四件事:
- 垃圾回收的工作,比如清除數(shù)據(jù)和消息育瓜,移除回調(diào)葫隙。
- 通過Session的remove方法刪除Window,mWindowSession.remove(mWindow)躏仇,這同樣是一個IP C過程恋脚,最終會調(diào)用WindowManagerService的removeWindow方法
- 調(diào)用View的dispatchDetachedFromWindow方法腺办,在內(nèi)部調(diào)用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。
- 調(diào)用WindowManagerGlobal的doRemoveView方法刷新數(shù)據(jù)糟描,包括mRoots怀喉、mParams以及mDyingViews,需要將當前Window所關(guān)聯(lián)的這三類對象從列表中刪除船响。
Window的更新過程
首先需要更新View的LayoutParams并替換掉老的LayoutParams躬拢,接著再更新ViewRootImpl中的LayoutParams,這一步是通過ViewRootImpl的setLayoutParams方法來實現(xiàn)的见间。在ViewRootImpl中會通過scheduleTrversals方法來對View重新布局聊闯,包括測量、布局米诉、重繪三個過程菱蔬。除了View本身的重繪以外,ViewRootImpl還會通過WindowSession來更新Window的視圖荒辕,這個過程最終是由WindowManagerService的relayoutWindow()來具體實現(xiàn)的汗销,同樣是一個IPC過程。
Window的創(chuàng)建過程
Activity的Window創(chuàng)建過程
1抵窒、Activity的啟動過程很復(fù)雜弛针,最終會由ActivityThread中的performLaunchActivity()來完成整個啟動過程,在這個方法內(nèi)部會通過類加載器創(chuàng)建Activity的實例對象李皇,并調(diào)用其attach方法為其關(guān)聯(lián)運行過程中所依賴的一系列上下文環(huán)境變量削茁。
2、Activity實現(xiàn)了Window的Callback接口掉房,當Window接收到外界的狀態(tài)變化時就會調(diào)用Activity的方法茧跋,例如onAttachedToWindow、onDetachedFromWindow卓囚、dispatchTouchEvent等瘾杭。
3、Activity的Window是由PolicyManager來創(chuàng)建的哪亿,它的真正實現(xiàn)是Policy類粥烁,它會新建一個PhoneWindow對象,Activity的setContentView的實現(xiàn)是由PhoneWindow來實現(xiàn)的蝇棉。
PhoneWindow方法大致遵循如下幾個步驟:
- 如果沒有DecorView讨阻,那么就創(chuàng)建它
- 將View添加到DecorView的mContentParent中
- 回調(diào)Activity的onCreateChanged方法通知Activity視圖已經(jīng)發(fā)生改變
Dialog的Window創(chuàng)建過程
Dialog的Window的創(chuàng)建過程和Activity類似,有如下步驟:
- 創(chuàng)建Window: Diolog中Window的創(chuàng)建同樣是通過PolicyManager的makeNewWindow方法來完成的篡殷,創(chuàng)建后的對象實際上就是PhoneWindow钝吮。
- 初始化DecorView并將Dialog的視圖添加到DecorView中
- 將DecorView添加到Window中并顯示:普通的Dialog有一個特殊之處,就是必須采用Activity的Context,如果采用Application的Context奇瘦,那么就會報錯棘催。應(yīng)用token只有Activity擁有,所以這里只需要Activity作為Context來顯示對話框即可链患。
Toast的Window創(chuàng)建過程
在Toast的內(nèi)部有兩類IPC過程巧鸭,第一類是Toast訪問NotificationManagerService,第二類是NotificationManagerService回調(diào)Toast里的TN接口麻捻。
Toast屬于系統(tǒng)Window纲仍,它內(nèi)部的視圖由兩種方式指定:一種是系統(tǒng)默認的演示,另一種是通過setView方法來指定一個自定義的View
Toast具有定時取消功能贸毕,所以系統(tǒng)采用了Handler郑叠。Toast的顯示和隱藏是IPC過程,都需要NotificationManagerService(NMS)來實現(xiàn)明棍,在Toast和NMS進行IPC過程時乡革,NMS會跨進程回調(diào)Toast中的TN類中的方法,TN類是一個Binder類摊腋,運行在Binder線程池中沸版,所以需要通過Handler將其切換到當前發(fā)送Toast請求所在的線程,所以Toast無法在沒有Looper的線程中彈出兴蒸。
對于非系統(tǒng)應(yīng)用來說视粮,mToastQueue最多能同時存在50個ToastRecord,這樣做是為了防止DOS(Denial of Service橙凳,拒絕服務(wù))蕾殴。因為如果某個應(yīng)用彈出太多的Toast會導(dǎo)致其他應(yīng)用沒有機會彈出Toast。