學(xué)習(xí)筆記|《Android開發(fā)藝術(shù)探索》第八章

理解Window和WindowManager

Window是一個抽象類胜茧,它的具體實(shí)現(xiàn)是PhoneWindow。WindowManager是外界訪問Window的入口仇味,Window的具體實(shí)現(xiàn)位于WindowManagerService中呻顽,WindowManager和WindowManagerService的交互是一個IPC過程。Android中所有的視圖都是通過Window來呈現(xiàn)的丹墨,不管是Activity廊遍、Dialog還是Toast,它們的視圖實(shí)際上都是附加在Window上的贩挣,因此Window實(shí)際是View的直接管理者喉前。

8.1 Window和WindowManager

為了分析Window的工作機(jī)制,先通過代碼了解如何使用WindowManager添加一個Window揽惹,下面一段代碼將一個Button添加到屏幕坐標(biāo)為(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的屬性四康,以下列舉常用的選項(xiàng):

  • FLAG_NOT_FOCUSABLE:表示W(wǎng)indow不需要獲取焦點(diǎn)搪搏,也不需要接收各種輸入事件,此標(biāo)記會同時啟動FLAG_NOT_TOUCH_MODEL闪金,最終事件會傳遞給下層的具有焦點(diǎn)的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:開啟此模式可以讓顯示在鎖屏的界面

Type參數(shù)表示W(wǎng)indow的類型职祷,Window有三種類型佩迟,分別是應(yīng)用Window菲盾、子Window和系統(tǒng)Window郑口。應(yīng)用類Window對應(yīng)著一個Activity鸳碧。子Window不能單獨(dú)存在,它需要附屬在特定的父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朝墩。

8.2 Window的內(nèi)部機(jī)制

Window是一個抽象的概念醉拓,并不是實(shí)際存在的,它是以View的形式存在收苏,每一個Window都對應(yīng)著一個View和一個ViewRootImpl亿卤,Window和View通過ViewRootImpl來建立聯(lián)系。在實(shí)際使用中無法直接訪問Window鹿霸,對Window的訪問必須通過WindowManager排吴。

8.2.1 Window的添加過程

Window的添加過程需要通過WindowManager的addView來實(shí)現(xiàn),WindowManager是一個接口懦鼠,它的真正實(shí)現(xiàn)是WindowManagerImpl類钻哩。WindowManager的實(shí)現(xiàn)類對于addView、updateView和removeView方法都是委托給WindowManagerGlobal類肛冶。

WindowManagerGlobal的addView方法分為如下幾步:

  1. 檢查參數(shù)是否合法街氢,如果是子Window那么還需要調(diào)整一些布局參數(shù)
  2. 創(chuàng)建ViewRootImpl并將View添加到列表中
  3. 通過ViewRootImpl來更新界面并完成Window的添加過程

8.2.2 Window的刪除過程

和添加過程一樣,都是先通過WindowManagerImpl后睦袖,再進(jìn)一步通過WindowManagerGlobal來實(shí)現(xiàn)的珊肃。
真正刪除View的邏輯在dispatchDetachedFromWindow方法的內(nèi)部實(shí)現(xiàn)。dispatchDetachedFromWindow方法主要做四件事:

  1. 垃圾回收的工作馅笙,比如清除數(shù)據(jù)和消息伦乔,移除回調(diào)。
  2. 通過Session的remove方法刪除Window延蟹,mWindowSession.remove(mWindow)评矩,這同樣是一個IP C過程,最終會調(diào)用WindowManagerService的removeWindow方法
  3. 調(diào)用View的dispatchDetachedFromWindow方法阱飘,在內(nèi)部調(diào)用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()斥杜。
  4. 調(diào)用WindowManagerGlobal的doRemoveView方法刷新數(shù)據(jù)虱颗,包括mRoots、mParams以及mDyingViews蔗喂,需要將當(dāng)前Window所關(guān)聯(lián)的這三類對象從列表中刪除忘渔。

8.2.3 Window的更新過程

首先需要更新View的LayoutParams并替換掉老的LayoutParams,接著再更新ViewRootImpl中的LayoutParams缰儿,這一步是通過ViewRootImpl的setLayoutParams方法來實(shí)現(xiàn)的畦粮。在ViewRootImpl中會通過scheduleTrversals方法來對View重新布局,包括測量乖阵、布局宣赔、重繪三個過程。除了View本身的重繪以外瞪浸,ViewRootImpl還會通過WindowSession來更新Window的視圖儒将,這個過程最終是由WindowManagerService的relayoutWindow()來具體實(shí)現(xiàn)的,同樣是一個IPC過程对蒲。

Window的創(chuàng)建過程

8.3.1 Activity的Window創(chuàng)建過程

1钩蚊、Activity的啟動過程很復(fù)雜,最終會由ActivityThread中的performLaunchActivity()來完成整個啟動過程蹈矮,在這個方法內(nèi)部會通過類加載器創(chuàng)建Activity的實(shí)例對象砰逻,并調(diào)用其attach方法為其關(guān)聯(lián)運(yùn)行過程中所依賴的一系列上下文環(huán)境變量。

2泛鸟、Activity實(shí)現(xiàn)了Window的Callback接口蝠咆,當(dāng)Window接收到外界的狀態(tài)變化時就會調(diào)用Activity的方法,例如onAttachedToWindow谈况、onDetachedFromWindow勺美、dispatchTouchEvent等。

3碑韵、Activity的Window是由PolicyManager來創(chuàng)建的,它的真正實(shí)現(xiàn)是Policy類祝闻,它會新建一個PhoneWindow對象,Activity的setContentView的實(shí)現(xiàn)是由PhoneWindow來實(shí)現(xiàn)的遗菠。
PhoneWindow方法大致遵循如下幾個步驟:

  1. 如果沒有DecorView,那么就創(chuàng)建它
  2. 將View添加到DecorView的mContentParent中
  3. 回調(diào)Activity的onCreateChanged方法通知Activity視圖已經(jīng)發(fā)生改變

8.3.2 Dialog的Window創(chuàng)建過程

Dialog的Window的創(chuàng)建過程和Activity類似,有如下步驟:

  1. 創(chuàng)建Window:Diolog中Window的創(chuàng)建同樣是通過PolicyManager的makeNewWindow方法來完成的,創(chuàng)建后的對象實(shí)際上就是PhoneWindow。
  2. 初始化DecorView并將Dialog的視圖添加到DecorView中
  3. 將DecorView添加到Window中并顯示:普通的Dialog有一個特殊之處帚湘,就是必須采用Activity的Context,如果采用Application的Context恒傻,那么就會報(bào)錯沸手。應(yīng)用token只有Activity擁有捐晶,所以這里只需要Activity作為Context來顯示對話框即可。

8.3.3 Toast的Window創(chuàng)建過程

在Toast的內(nèi)部有兩類IPC過程干花,第一類是Toast訪問NotificationManagerService修赞,第二類是NotificationManagerService回調(diào)Toast里的TN接口。

Toast屬于系統(tǒng)Window,它內(nèi)部的視圖由兩種方式指定:一種是系統(tǒng)默認(rèn)的演示,另一種是通過setView方法來指定一個自定義的View

Toast具有定時取消功能,所以系統(tǒng)采用了Handler趾代。Toast的顯示和隱藏是IPC過程糯俗,都需要NotificationManagerService(NMS)來實(shí)現(xiàn)顿仇,在Toast和NMS進(jìn)行IPC過程時囤采,NMS會跨進(jìn)程回調(diào)Toast中的TN類中的方法思犁,TN類是一個Binder類衙傀,運(yùn)行在Binder線程池中任洞,所以需要通過Handler將其切換到當(dāng)前發(fā)送Toast請求所在的線程蓄喇,所以Toast無法在沒有Looper的線程中彈出。

對于非系統(tǒng)應(yīng)用來說交掏,mToastQueue最多能同時存在50個ToastRecord妆偏,這樣做是為了防止DOS(Denial of Service,拒絕服務(wù))盅弛。因?yàn)槿绻硞€應(yīng)用彈出太多的Toast會導(dǎo)致其他應(yīng)用沒有機(jī)會彈出Toast钱骂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挪鹏,隨后出現(xiàn)的幾起案子见秽,更是在濱河造成了極大的恐慌,老刑警劉巖讨盒,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件解取,死亡現(xiàn)場離奇詭異,居然都是意外死亡返顺,警方通過查閱死者的電腦和手機(jī)禀苦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門蔓肯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人振乏,你說我怎么就攤上這事蔗包。” “怎么了慧邮?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵调限,是天一觀的道長。 經(jīng)常有香客問我赋咽,道長旧噪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任脓匿,我火速辦了婚禮淘钟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陪毡。我一直安慰自己米母,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布毡琉。 她就那樣靜靜地躺著铁瞒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪桅滋。 梳的紋絲不亂的頭發(fā)上慧耍,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音丐谋,去河邊找鬼芍碧。 笑死,一個胖子當(dāng)著我的面吹牛号俐,可吹牛的內(nèi)容都是我干的泌豆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼吏饿,長吁一口氣:“原來是場噩夢啊……” “哼踪危!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起猪落,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤贞远,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后笨忌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兴革,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年蜜唾,在試婚紗的時候發(fā)現(xiàn)自己被綠了杂曲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡袁余,死狀恐怖擎勘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颖榜,我是刑警寧澤棚饵,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站掩完,受9級特大地震影響噪漾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜且蓬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一欣硼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恶阴,春花似錦诈胜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至昵仅,卻和暖如春缓熟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摔笤。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工够滑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人籍茧。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓版述,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寞冯。 傳聞我的和親對象是個殘疾皇子渴析,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容