概述
- Window是一個抽象類亲桥,具體實現(xiàn)是PhoneWindow。
- 創(chuàng)建一個Window,通過WindowManger就可以完成狭归。WindowMangager是外界訪問Window的入口
- Window的具體實現(xiàn)位于WindowMangerService(WMS)睛蛛,WindowManager和WMS的交互是一個IPC過程鹦马。
- Activity/Dialog/Toast,他們的視圖都是附加在Window上的忆肾。Window是View的直接管理者荸频。
8.1 Window和WindowManager
- 通過WindowManager添加Window的過程
mFloatingButton = new Button(this);
mFloatingButton.setText("test button");
mLayoutParams = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,
PixelFormat.TRANSPARENT);
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ù)解析:
- FLAG_NOT_FOCUSABLE:表示window不需要獲取焦點,也不需要接收各種輸入事件客冈。
- FLAG_NOT_TOUCH_MODAL:系統(tǒng)會將window區(qū)域外的單擊事件傳遞給底層的window旭从,一般都需要開啟這個標記;
- FLAG_SHOW_WHEN_LOCKED:開啟此模式可以讓Window顯示在鎖屏的界面上场仲。
-
type參數(shù)表示window的類型和悦,window共有三種類型:
- 應用window。應用window對應著一個Activity渠缕,層級范圍是1~99
- 子window鸽素。子window不能獨立存在,需要附屬在特定的父window之上亦鳞,比如Dialog就是子window付鹿。層級范圍是1000~1999.
- 系統(tǒng)window。系統(tǒng)window是需要聲明權(quán)限才能創(chuàng)建的window蚜迅,比如Toast和系統(tǒng)狀態(tài)欄這些都是系統(tǒng)window舵匾,需要聲明的權(quán)限是<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />。層級范圍是2000~2999
-
WindowManager繼承自ViewManager谁不,常用的只有三個方法:
- addView
- updateViewLayout
- removeView
8.2 Window的內(nèi)部機制
- Window是一個抽象的概念坐梯,每個Window都對應一個View和ViewRootImpl,Window和View通過ViewRootImpl聯(lián)系刹帕,Window是以View的形式存在吵血。
- WindowManger是一個接口,真正實現(xiàn)是WindowManagerImpl類偷溺,WindowManagerImpl交給WindowMangerGlobal蹋辅。WindowMangerGlobal以工廠的形式提供自己的實例。
- 此處是橋接模式挫掏,WindowManagerImple將所有的操作全部委托給WindowManagerGlobal實現(xiàn)侦另。
8.2.1 Window的添加過程
- 檢查參數(shù)是否合法
- 創(chuàng)建ViewRootImpl將View添加到列表中
- 通過ViewRootImple更新界面并完成Window的添加過程。
- 在這里的setViez中會通過requesetLayout完成異步刷新請求。
- 會通過WindowSession對WMS進行Binder調(diào)用褒傅,由WMS實現(xiàn)Window的添加弃锐。WMS會對每個應用保留一個單獨的Session.
8.2.2 Window的刪除過程
- 通過findViewLocked查找待刪除的View的索引
- 調(diào)用removeViewLocked刪除
- 真正刪除View的邏輯在dispatchDetachedFromWindow
- 垃圾回收
- 通過Session的remove刪除Window,也是一個IPC過程
- onDetachedFromWindow(內(nèi)部資源回收殿托,比如停止線程霹菊、終止動畫)
- 刷新mViews、mRoots旋廷、mDyingViews等數(shù)據(jù)
8.2.3 Window的更新過程
- 更新View的LayoutParams
- 更新ViewRootImpl的LayoutParams
- 對View重新布局,包括measure礼搁、layout柳洋、draw三個過程
8.3 Window的創(chuàng)建過程
8.3.1 Activity的window創(chuàng)建過程
- Activity的啟動過程最終會由ActivityThread中的performLaunchActivity來完成整個啟動過程,在這個方法內(nèi)部會通過類加載器創(chuàng)建Activity的實例對象叹坦,并調(diào)用它的attach方法為其關(guān)聯(lián)運行過程中所依賴的一系列上下文環(huán)境變量熊镣;
- Activity實現(xiàn)了Window的Callback接口,當window接收到外界的狀態(tài)變化時就會回調(diào)Activity的方法募书,例如onAttachedToWindow绪囱、onDetachedFromWindow、dispatchTouchEvent等莹捡;
- Activity的Window是由PolicyManager來創(chuàng)建的鬼吵,它的真正實現(xiàn)是Policy類,它會新建一個PhoneWindow對象篮赢,Activity的setContentView的實現(xiàn)是由PhoneWindow來實現(xiàn)的齿椅;
- Activity的頂級View是DecorView,它本質(zhì)上是一個FrameLayout启泣。如果沒有DecorView涣脚,那么PhoneWindow會先創(chuàng)建一個DecorView,然后加載具體的布局文件并將view添加到DecorView的mContentParent中寥茫,最后就是回調(diào)Activity的onContentChanged通知Activity視圖已經(jīng)發(fā)生了變化遣蚀;
- 讓WindowManager能夠識別DecorView,在ActivityThread調(diào)用handleResumeActivity方法時纱耻,首先會調(diào)用Activity的onResume方法芭梯,然后會調(diào)用makeVisible方法,這個方法中DecorView真正地完成了添加和顯示過程弄喘。
8.3.2 Dialog的Window創(chuàng)建過程
- 創(chuàng)建Window 玖喘。通過PolicyManager的makeNewWindow完成
- setContentView,初始化DecorView蘑志,并將Dialog視圖放到DecorView中
- 將DecorView放到Window中顯示
- 當Dialog關(guān)閉累奈,通過WindowManger移除DecorView
- 普通Dialog必須才有Activity的Context贬派,應用Token只有Activity擁有。系統(tǒng)window不需要應用Token
8.3.3 Toast的Window創(chuàng)建過程
- Toast屬于系統(tǒng)Window费尽,它內(nèi)部的視圖由兩種方式指定:一種是系統(tǒng)默認的演示;另一種是通過setView方法來指定一個自定義的View羊始。
- Toast的顯示和隱藏是IPC過程旱幼,都需要NotificationManagerService來實現(xiàn)。在Toast和NMS進行IPC過程時突委,NMS會跨進程回調(diào)Toast中的TN類中的方法柏卤,TN類是一個Binder類,運行在Binder線程池中匀油,所以需要通過Handler將其切換到當前發(fā)送Toast請求所在的線程缘缚,所以Toast無法在沒有Looper的線程中彈出。
- 對于非系統(tǒng)應用來說敌蚜,mToastQueue最多能同時存在50個ToastRecord桥滨,這樣做是為了防止DOS(Denial of Service,拒絕服務)弛车。因為如果某個應用彈出太多的Toast會導致其他應用沒有機會彈出Toast齐媒。