Android中Window的管理深入講解
一、理解 Android 的 Window
Window 表示一個(gè)窗口的概念,是一個(gè)抽象的概念,每一個(gè) Window 都對(duì)應(yīng)一個(gè) View 和一個(gè) ViewRootImpl,Window 和 View 通過(guò) ViewRootImpl 來(lái)建立聯(lián)系,因此 Window 并不是實(shí)際存在的,它是以 View 的形式存在。
Android 中的每個(gè)窗口 View 都有一個(gè)對(duì)應(yīng)的 Window,例如 Activity、Dialog,在他們初始化的時(shí)候就會(huì)為其創(chuàng)建對(duì)應(yīng)的PhoneWindow 并賦值到其內(nèi)部的一個(gè)引用
window 的層級(jí)
WindowLayoutParams.setType 設(shè)置
每個(gè) window 都有其對(duì)應(yīng)的層級(jí),應(yīng)用 window 在 1-99,子 window 在 1000-1999,系統(tǒng) window 在 2000-2999 ,層級(jí)高的會(huì)覆蓋層級(jí)低的
子 window 必須依賴(lài)于父 window 存在,例如 Dialog 必須在 Activity 中彈出,Dialog 中的 window 為子 window ,Activity 中的 window 為父 window
顯示系統(tǒng)級(jí)別的 window 需要權(quán)限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
WindowLayoutparams 的 flags
FLAG_NOT_FOUCSABLE window 不需要獲取焦點(diǎn),也不需要接收各種輸入事件,回同時(shí)啟用 FLAG_NOT_TOUCH_MODAL
FLAG_NOT_TOUCH_MODAL 系統(tǒng)會(huì)將當(dāng)前 Window 區(qū)域外的單擊事件傳遞給底層的 Window,在當(dāng)前 Window 區(qū)域內(nèi)的事件則自己處理
FLAG_SHOW_WHEN_LOCKED 開(kāi)啟此模式讓 window 顯示在鎖屏界面上
二、理解 Android 中的 WindowManager
Android 中對(duì) Window 的管理都是通過(guò) WindowManager 來(lái)完成的,創(chuàng)建 PhoneWindow 之后還會(huì)為該 Window 對(duì)象設(shè)置 WindowManager ,WindowManager 是一個(gè)接口繼承 ViewManager 接口,從這里也能看出對(duì) Window 的操作其實(shí)就是對(duì) View 的操作,WindowManager 的實(shí)現(xiàn)類(lèi)是 WindowMangerImpl ,WindowMangerImpl 通過(guò) new 創(chuàng)建。
三、Window 與 WindowManagerImpl 的關(guān)聯(lián)
通過(guò) ContextImpl 的 getSystemService 可以得到 WindowManagerImpl 實(shí)例,同一 ContextImpl 得到的是同一個(gè)WindowManagerImpl對(duì)象,得到 WindowMangerImpl 之后,調(diào)用 Window 的 setWindowManager 方法建立 Window 與 WindowManagerImpl 之間的聯(lián)系。
setWindowManager 中主要完成在 WindowManagerImpl 實(shí)例的基礎(chǔ)上重新創(chuàng)建一個(gè)與當(dāng)前 Window 綁定的 WindowManagerImpl,并為 Window 中的屬性 mWindowManager 賦值
也就是說(shuō)在 Java 層上 Window 與 WindowManager 建立了第一步聯(lián)系,并將 Activity、Dialog 等中的 WindowManager 賦值為新的 WindowManagerImpl 對(duì)象。
注意:這里是使用單例的 WindowManagerImpl ,結(jié)合不同的 Window ,最后構(gòu)建了與 Window 有關(guān)聯(lián)的非單例的 WindowManagerImpl 對(duì)象
四、對(duì) Window 的操作
1. 添加操作 WindowManagerImpl.addView,注意,是添加一個(gè)新的 Window ,不是對(duì)一個(gè) Window 中的 view 做操作
Android 中每顯示一個(gè)窗口,其實(shí)就是將 View 顯示到屏幕的過(guò)程,如果我們自定義一個(gè)要顯示的布局,拿到 View 對(duì)象,這時(shí)候只要調(diào)用 WindowManagerImpl 對(duì)象的 addView 方法就行了,通過(guò) ContextImpl 的 getSystemService 可以得到 WindowManagerImpl 實(shí)例
WindowManagerImpl 對(duì)象,在 WindowManagerImpl 中存在一個(gè)單例存在的 WindowManagerGlobal 對(duì)象,在 WindowManagerImpl 的各個(gè)方法中,將任務(wù)的執(zhí)行過(guò)程傳遞到了 WindowManagerGlobal 中,在傳遞過(guò)程中除了將 View、LayoutParams 傳遞,還將 WindowManagerImpl 中關(guān)聯(lián)的 window 對(duì)象也一起傳遞
WindowManagerGlobal 的 addView 方法
WindowMAnagerGlobal 中判斷 view、LayoutParams 等參數(shù)合法性,創(chuàng)建 ViewRootImpl ,將 ViewRootImpl、View、LayoutParams 添加到 WindowMAnagerGlobal 中對(duì)應(yīng)的 ArayyList 集合中,再調(diào)用 ViewRootImpl 的 setView 方法
ViewRootImpl
繼承自 Handler 類(lèi),是作為 native 層和 Java 層 View 系統(tǒng)通信的橋梁
ViewRootImpl 創(chuàng)建時(shí)保存了創(chuàng)建其的線程的引用,開(kāi)發(fā)過(guò)程中更新 View 時(shí)會(huì)判斷當(dāng)前線程是否是創(chuàng)建 ViewRootImpl 的線程,如果不是會(huì)拋出異常。
一般都是在主線程中創(chuàng)建 ViewRootImpl ,所以在子線程更新 UI 會(huì)拋出異常,是因?yàn)?ViewRootImpl 是 UI 線程中創(chuàng)建的,并不是因?yàn)橹挥?UI 線程才可以更新 UI
在 Activity 的 onResume 之前如果在子線程中修改 UI 是不會(huì)拋出異常的,因?yàn)樵?onResume 之后才創(chuàng)建 ViewRootImpl,這時(shí)更新 UI 需要經(jīng)過(guò) ViewRootImpl 來(lái)更新,在 onResume 之前 Activity 的屏幕并沒(méi)有顯示,修改 UI 操作只是會(huì)修改 layout 中的 UI,并不會(huì)調(diào)用 ViewRootImpl 的方法顯示到屏幕上。
所以得出結(jié)論,只有 UI 顯示到屏幕上之后,在更新 UI 時(shí)就會(huì)判斷線程是否為創(chuàng)建 UI 的線程,如果不匹配則拋出異常,在 UI 沒(méi)有顯示到屏幕上時(shí)更新 UI 是不會(huì)進(jìn)行線程判斷的
ViewRootImpl 的 setView 方法:
- setView 方法中會(huì)首先調(diào)用 requestLayout() 方法,在這里進(jìn)行線程判斷,如果線程匹配則調(diào)用 scheduleTraversals() 完成 View 的測(cè)量、布局、繪制過(guò)程
- setView 中接下來(lái)遠(yuǎn)程調(diào)用 IWindowSession 對(duì)象中的 addToDisplay 方法,將 window 等信息傳遞到 NMS 中調(diào)用 NMS 的 addWindow 方法完成最后window 在屏幕上的展示。
IWindowSession WindowManagerService
這里是將 View 顯示到屏幕上的關(guān)鍵,是將 View 從應(yīng)用進(jìn)程傳遞到系統(tǒng)進(jìn)程然后完成顯示的地方。 ViewRootImpl 中的 IWindowSession 對(duì)象是通過(guò) WindowManagerGlobal 的靜態(tài)方法 getWindowSession() 得到的,該方法中首先通過(guò) ServiceManager 通過(guò) Binder 進(jìn)行 IPC 得到系統(tǒng)服務(wù) WindowsManagerService 在應(yīng)用進(jìn)程的遠(yuǎn)程代理,然后通過(guò) AIDL 通信的方式調(diào)用 WMS 的 openSession() 方法得到 IWindowSession 接口的實(shí)現(xiàn)類(lèi) Session 類(lèi)遠(yuǎn)程代理對(duì)象,Session 類(lèi)也是一個(gè) Binder 所以可以跨進(jìn)程傳遞
在 Session 的遠(yuǎn)程代理的 addToDisplay 方法中通過(guò) AIDL 調(diào)用 Session 的 addToDisplay 方法將 window 信息傳遞到系統(tǒng)進(jìn)程,然后調(diào)用 AMS 的 addWindow 方法,AMS 最后將 Window 中的 View 顯示到屏幕上
2. 刪除操作,是刪除一個(gè)屏幕上已有的 Window
WindowManagerImpl.removeView 操作中,先通過(guò) findViewLocked 查找要?jiǎng)h除的 View,再通過(guò) View 找到對(duì)應(yīng)的 Window 的 ViewRootImpl ,將 View、LayoutParams、ViewRootImpl 從相對(duì)應(yīng)的 ArrayList 中刪除,再通過(guò) IPC 調(diào)用 Session 的 remove 其中調(diào)用 WMS 的 removeWindow 方法,在屏幕上移除該 Window 對(duì)應(yīng)的 View
3. 更新操作
WindowManagerImpl.updateViewLayout ,為 view 設(shè)置新的 LayoutParams ,通過(guò) findViewLocked 找到對(duì)應(yīng) ViewRootImpl,刪除 LayoutParams 集合中舊的 LayoutParams,在集合原位置加入 新的 LayoutParams,調(diào)用 ViewRootImpl 的 setLayoutParams 完成 View 的重新測(cè)量,布局,繪制,最后通過(guò) IPC 調(diào)用 Session 再調(diào)用 WMS 完成 window 的更新。
4. 添加 Window 代碼
自定義的 Window 在創(chuàng)建過(guò)程中并沒(méi)有主動(dòng)的創(chuàng)建 Window,而是在顯示的時(shí)候由系統(tǒng)維護(hù),這里也體現(xiàn)了 Window 是一個(gè)抽象的概念,最終需要處理的還是 View
private void addWindow() { TextView view = new TextView(this); view.setText("Text"); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i("renxl", "onClick"); } }); view.setBackgroundColor(Color.RED); // 要顯示的 View 可以是新創(chuàng)建的,也可以是 LayoutInflater 從 xml 布局中獲取的 WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT); // 必須是 WindowManager.LayoutParams mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; // 三種 flag 從中選一 mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST; // type 表示優(yōu)先級(jí) mLayoutParams.gravity = Gravity.START | Gravity.TOP; mLayoutParams.x = 100; // 在屏幕上 X 軸位置 mLayoutParams.y = 300; // 在屏幕上 Y 軸位置 WindowManager manager = (WindowManager) getSystemService(WINDOW_SERVICE); manager.addView(view, mLayoutParams); // 將 View 添加到界面上 } **注意,如果是系統(tǒng)級(jí)別的 Window 也就是優(yōu)先級(jí)超過(guò) 1999 的,需要聲明權(quán)限** <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
五、用戶觸摸屏幕事件處理流程
WMS 將事件 IPC 傳遞到 Window,Window 中調(diào)用其內(nèi)部的 CallBack 對(duì)象也就是 Activity 或者 Dialog 對(duì)象或者的方法。最終將事件傳遞到 View,再通過(guò) ViewRootImpl 將響應(yīng)以后的 View 和對(duì)應(yīng) window IPC 提交到 WMS 完成響應(yīng)后的展示。
六、常見(jiàn) Window 的創(chuàng)建
1. Activity 的 Window 創(chuàng)建過(guò)程
Activity 啟動(dòng)流程中,Activity 的 attach 方法中為 window 賦值為一個(gè)新的 PhoneWindow ,setContentView 將 layout 傳遞到 PhoneWindow 中,PhoneWindow 中通過(guò) LayoutInflater 的 inflate 方法加載布局 View,并添加到 PhoneWindow 內(nèi)部的 DecorView 中,在 onResume 的時(shí)候,調(diào)用 WindowManager 的 addView 方法將 Window 中的 DecorView 通過(guò) WMS 顯示到屏幕上
Activity 在創(chuàng)建 Window 的時(shí)候,實(shí)現(xiàn)了 Window 的 Callback 接口中的方法,在 Window 收到觸摸時(shí),則會(huì)回調(diào) Callback 中的方法將事件傳遞到 Activity 中,Activity 中會(huì)調(diào)對(duì)應(yīng) PhoneWindow 中的分發(fā)方法,PhoneWindow 中會(huì)調(diào)用 DecordView 中的方法, 最終將事件傳遞到 View 中。
猜測(cè)事件由 WMS 傳遞到 Window 再到 Activity 再到 Window 這樣多一層 Activity 的原因是,開(kāi)發(fā)者可以在 Activity 中處理事件,不一定非要傳遞到 View
2. Dialog 的 Window 創(chuàng)建過(guò)程
同 Activity,實(shí)例化 Dialog 對(duì)象時(shí)創(chuàng)建 PhoneWindow ,show 方法調(diào)用時(shí)通過(guò) AIDL 調(diào)用 WMS 的 addView 方法將 View 添加到屏幕
3. Toast 的 Window 創(chuàng)建過(guò)程
Toast 在創(chuàng)建過(guò)程中并沒(méi)有主動(dòng)的創(chuàng)建 Window,而是在顯示的時(shí)候由系統(tǒng)維護(hù) Toast 的 window,這里也體現(xiàn)了 Window 是一個(gè)抽象的概念,最終需要處理的還是 View
Toast 的工作工程需要 TN NMS WMS 三個(gè)部分協(xié)同完成,IN 也是一個(gè) Binder,NMS 中調(diào)用 TN 是遠(yuǎn)程訪問(wèn),TN 調(diào)用 WMS 也是遠(yuǎn)程調(diào)用
NMS 即 NotificationManagerService
Toast 的工作過(guò)程分為兩步
- 第一步是當(dāng)前線程中要先生成一個(gè) Binder 類(lèi)型的 TN 的對(duì)象,遠(yuǎn)程調(diào)用 NMS 中的 enquneue 方法將 TN 傳到 NMS 中,NMS 遠(yuǎn)程調(diào)用 TN 的 show 方法,TN 中 show 方法運(yùn)行在當(dāng)前應(yīng)用的 Binder 線程池中,通過(guò) Handler 的 post 系列方法將進(jìn)程切換到主線程,主線程再通過(guò) WindowManager 來(lái)調(diào)用 WMS 中的方法完成 show 過(guò)程
- NMS 中調(diào)用了 TN 的 show 方法之后,會(huì)通過(guò)自己內(nèi)部的 Handler 延時(shí)發(fā)送一個(gè)時(shí)間為 Toast 展示時(shí)間的消息,NMS 中的 Handler 收到消息之后,再調(diào)用 TN 的 hide 方法(遠(yuǎn)程調(diào)用過(guò)程),TN 中的 hide 方法又會(huì)通過(guò) WindowManager 遠(yuǎn)程調(diào)用 WMS 中的 hide 方法,將 Toast 隱藏。完成整個(gè)過(guò)程。
七、總結(jié)
屏幕展示的每一個(gè) window,都需要 window 和 View 兩個(gè)相互結(jié)合,屏幕中可以有多個(gè) Window。以下所說(shuō)的 View 都是一個(gè) Window 中包含的根 View
window 的創(chuàng)建以及對(duì) View 的添加,刪除、更新是由 WindowManager 來(lái)實(shí)現(xiàn)的,而 WindowManager 中對(duì) window 的操作通過(guò) 每個(gè) window 對(duì)應(yīng)的 ViewRootImpl 中通過(guò) IPC 遠(yuǎn)程請(qǐng)求 IWindowSession 中的方法再調(diào)用 WMS 的對(duì)應(yīng)方法將對(duì)當(dāng)前 window 操作的實(shí)現(xiàn)到屏幕上。
每一個(gè) Window 都對(duì)應(yīng)一個(gè) ViewRootImpl ,window 通過(guò)對(duì)應(yīng)的 ViewRootImpl 來(lái)完成對(duì) view 的管理
在屏幕有用戶交互的時(shí)候,WMS 又會(huì)將事件傳遞到相應(yīng)界面的 Window,Window 會(huì)調(diào)用當(dāng)前界面的對(duì)應(yīng)的 CallBack 來(lái)處理事件
WindowManager 是接口,實(shí)現(xiàn)類(lèi)是 WindowManagerImpl,WindowManagerImpl 中又通過(guò) WindowMAnagerGlobal 來(lái)完成操作。典型的橋接模式
添加 Window 顯示不出來(lái)問(wèn)題
由于國(guó)內(nèi)對(duì)于 ROM 的定制,多種機(jī)型會(huì)默認(rèn)禁止應(yīng)用對(duì)懸浮窗的創(chuàng)建,所以如果是沒(méi)有顯示,檢查是否關(guān)閉了應(yīng)用的權(quán)限。
- 安卓 6.0 添加了對(duì)權(quán)限的開(kāi)關(guān)設(shè)置,懸浮窗權(quán)限默認(rèn)是關(guān)閉的
- 一些國(guó)內(nèi)定制的 Rom 6.0 之前就可以設(shè)置權(quán)限的開(kāi)關(guān),懸浮窗權(quán)限默認(rèn)關(guān)閉
問(wèn)題解決
mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
將 type 設(shè)置為 TYPE_TOAST , 源碼中對(duì) TYPE_TOAST 是沒(méi)有任何限制的。
在國(guó)內(nèi)定制的 Rom 上,只有少數(shù)機(jī)型會(huì)在設(shè)置 TYPE_TOAST 的時(shí)候,View 的監(jiān)聽(tīng)事件不能獲取,顯示都是可以的。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Android中使用IntentService創(chuàng)建后臺(tái)服務(wù)實(shí)例
這篇文章主要介紹了Android中使用IntentService創(chuàng)建后臺(tái)服務(wù)實(shí)例,IntentService提供了在單個(gè)后臺(tái)線程運(yùn)行操作的簡(jiǎn)單結(jié)構(gòu),需要的朋友可以參考下2014-06-06android okhttp的基礎(chǔ)使用【入門(mén)推薦】
本文主要總結(jié)了Android著名網(wǎng)絡(luò)框架-okhttp的基礎(chǔ)使用。具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-01-01Android獲取本機(jī)電話號(hào)碼的簡(jiǎn)單方法
Android獲取本機(jī)電話號(hào)碼的簡(jiǎn)單方法,需要的朋友可以參考一下2013-05-05Android自定義viewGroup實(shí)現(xiàn)點(diǎn)擊動(dòng)畫(huà)效果
這篇文章主要介紹了Android自定義viewGroup實(shí)現(xiàn)點(diǎn)擊動(dòng)畫(huà)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Android實(shí)現(xiàn)調(diào)用手機(jī)攝像頭錄像限制錄像時(shí)長(zhǎng)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)調(diào)用手機(jī)攝像頭錄像限制錄像時(shí)長(zhǎng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03