您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何理解Android中Window的管理,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
一、理解 Android 的 Window
Window 表示一個窗口的概念,是一個抽象的概念,每一個 Window 都對應一個 View 和一個 ViewRootImpl,Window 和 View 通過 ViewRootImpl 來建立聯系,因此 Window 并不是實際存在的,它是以 View 的形式存在。
Android 中的每個窗口 View 都有一個對應的 Window,例如 Activity、Dialog,在他們初始化的時候就會為其創建對應的PhoneWindow 并賦值到其內部的一個引用
window 的層級
WindowLayoutParams.setType 設置
每個 window 都有其對應的層級,應用 window 在 1-99,子 window 在 1000-1999,系統 window 在 2000-2999 ,層級高的會覆蓋層級低的
子 window 必須依賴于父 window 存在,例如 Dialog 必須在 Activity 中彈出,Dialog 中的 window 為子 window ,Activity 中的 window 為父 window
顯示系統級別的 window 需要權限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
WindowLayoutparams 的 flags
FLAG_NOT_FOUCSABLE window 不需要獲取焦點,也不需要接收各種輸入事件,回同時啟用 FLAG_NOT_TOUCH_MODALFLAG_NOT_TOUCH_MODAL 系統會將當前 Window 區域外的單擊事件傳遞給底層的 Window,在當前 Window 區域內的事件則自己處理
FLAG_SHOW_WHEN_LOCKED 開啟此模式讓 window 顯示在鎖屏界面上
二、理解 Android 中的 WindowManager
Android 中對 Window 的管理都是通過 WindowManager 來完成的,創建 PhoneWindow 之后還會為該 Window 對象設置 WindowManager ,WindowManager 是一個接口繼承 ViewManager 接口,從這里也能看出對 Window 的操作其實就是對 View 的操作,WindowManager 的實現類是 WindowMangerImpl ,WindowMangerImpl 通過 new 創建。
三、Window 與 WindowManagerImpl 的關聯
通過 ContextImpl 的 getSystemService 可以得到 WindowManagerImpl 實例,同一 ContextImpl 得到的是同一個WindowManagerImpl對象,得到 WindowMangerImpl 之后,調用 Window 的 setWindowManager 方法建立 Window 與 WindowManagerImpl 之間的聯系。
setWindowManager 中主要完成在 WindowManagerImpl 實例的基礎上重新創建一個與當前 Window 綁定的 WindowManagerImpl,并為 Window 中的屬性 mWindowManager 賦值
也就是說在 Java 層上 Window 與 WindowManager 建立了第一步聯系,并將 Activity、Dialog 等中的 WindowManager 賦值為新的 WindowManagerImpl 對象。
注意:這里是使用單例的 WindowManagerImpl ,結合不同的 Window ,最后構建了與 Window 有關聯的非單例的 WindowManagerImpl 對象
四、對 Window 的操作
1. 添加操作 WindowManagerImpl.addView,注意,是添加一個新的 Window ,不是對一個 Window 中的 view 做操作
Android 中每顯示一個窗口,其實就是將 View 顯示到屏幕的過程,如果我們自定義一個要顯示的布局,拿到 View 對象,這時候只要調用 WindowManagerImpl 對象的 addView 方法就行了,通過 ContextImpl 的 getSystemService 可以得到 WindowManagerImpl 實例
WindowManagerImpl 對象,在 WindowManagerImpl 中存在一個單例存在的 WindowManagerGlobal 對象,在 WindowManagerImpl 的各個方法中,將任務的執行過程傳遞到了 WindowManagerGlobal 中,在傳遞過程中除了將 View、LayoutParams 傳遞,還將 WindowManagerImpl 中關聯的 window 對象也一起傳遞
WindowManagerGlobal 的 addView 方法
WindowMAnagerGlobal 中判斷 view、LayoutParams 等參數合法性,創建 ViewRootImpl ,將 ViewRootImpl、View、LayoutParams 添加到 WindowMAnagerGlobal 中對應的 ArayyList 集合中,再調用 ViewRootImpl 的 setView 方法
ViewRootImpl
繼承自 Handler 類,是作為 native 層和 Java 層 View 系統通信的橋梁
ViewRootImpl 創建時保存了創建其的線程的引用,開發過程中更新 View 時會判斷當前線程是否是創建 ViewRootImpl 的線程,如果不是會拋出異常。
一般都是在主線程中創建 ViewRootImpl ,所以在子線程更新 UI 會拋出異常,是因為 ViewRootImpl 是 UI 線程中創建的,并不是因為只有 UI 線程才可以更新 UI
在 Activity 的 onResume 之前如果在子線程中修改 UI 是不會拋出異常的,因為在 onResume 之后才創建 ViewRootImpl,這時更新 UI 需要經過 ViewRootImpl 來更新,在 onResume 之前 Activity 的屏幕并沒有顯示,修改 UI 操作只是會修改 layout 中的 UI,并不會調用 ViewRootImpl 的方法顯示到屏幕上。
所以得出結論,只有 UI 顯示到屏幕上之后,在更新 UI 時就會判斷線程是否為創建 UI 的線程,如果不匹配則拋出異常,在 UI 沒有顯示到屏幕上時更新 UI 是不會進行線程判斷的
ViewRootImpl 的 setView 方法:
setView 方法中會首先調用 requestLayout() 方法,在這里進行線程判斷,如果線程匹配則調用 scheduleTraversals() 完成 View 的測量、布局、繪制過程 setView 中接下來遠程調用 IWindowSession 對象中的 addToDisplay 方法,將 window 等信息傳遞到 NMS 中調用 NMS 的 addWindow 方法完成最后window 在屏幕上的展示。
IWindowSession WindowManagerService
這里是將 View 顯示到屏幕上的關鍵,是將 View 從應用進程傳遞到系統進程然后完成顯示的地方。 ViewRootImpl 中的 IWindowSession 對象是通過 WindowManagerGlobal 的靜態方法 getWindowSession() 得到的,該方法中首先通過 ServiceManager 通過 Binder 進行 IPC 得到系統服務 WindowsManagerService 在應用進程的遠程代理,然后通過 AIDL 通信的方式調用 WMS 的 openSession() 方法得到 IWindowSession 接口的實現類 Session 類遠程代理對象,Session 類也是一個 Binder 所以可以跨進程傳遞
在 Session 的遠程代理的 addToDisplay 方法中通過 AIDL 調用 Session 的 addToDisplay 方法將 window 信息傳遞到系統進程,然后調用 AMS 的 addWindow 方法,AMS 最后將 Window 中的 View 顯示到屏幕上
2. 刪除操作,是刪除一個屏幕上已有的 Window
WindowManagerImpl.removeView 操作中,先通過 findViewLocked 查找要刪除的 View,再通過 View 找到對應的 Window 的 ViewRootImpl ,將 View、LayoutParams、ViewRootImpl 從相對應的 ArrayList 中刪除,再通過 IPC 調用 Session 的 remove 其中調用 WMS 的 removeWindow 方法,在屏幕上移除該 Window 對應的 View
3. 更新操作
WindowManagerImpl.updateViewLayout ,為 view 設置新的 LayoutParams ,通過 findViewLocked 找到對應 ViewRootImpl,刪除 LayoutParams 集合中舊的 LayoutParams,在集合原位置加入 新的 LayoutParams,調用 ViewRootImpl 的 setLayoutParams 完成 View 的重新測量,布局,繪制,最后通過 IPC 調用 Session 再調用 WMS 完成 window 的更新。
4. 添加 Window 代碼
自定義的 Window 在創建過程中并沒有主動的創建 Window,而是在顯示的時候由系統維護,這里也體現了 Window 是一個抽象的概念,最終需要處理的還是 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 可以是新創建的,也可以是 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 表示優先級 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 添加到界面上}**注意,如果是系統級別的 Window 也就是優先級超過 1999 的,需要聲明權限**<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
五、用戶觸摸屏幕事件處理流程
WMS 將事件 IPC 傳遞到 Window,Window 中調用其內部的 CallBack 對象也就是 Activity 或者 Dialog 對象或者的方法。最終將事件傳遞到 View,再通過 ViewRootImpl 將響應以后的 View 和對應 window IPC 提交到 WMS 完成響應后的展示。
六、常見 Window 的創建
1. Activity 的 Window 創建過程
Activity 啟動流程中,Activity 的 attach 方法中為 window 賦值為一個新的 PhoneWindow ,setContentView 將 layout 傳遞到 PhoneWindow 中,PhoneWindow 中通過 LayoutInflater 的 inflate 方法加載布局 View,并添加到 PhoneWindow 內部的 DecorView 中,在 onResume 的時候,調用 WindowManager 的 addView 方法將 Window 中的 DecorView 通過 WMS 顯示到屏幕上
Activity 在創建 Window 的時候,實現了 Window 的 Callback 接口中的方法,在 Window 收到觸摸時,則會回調 Callback 中的方法將事件傳遞到 Activity 中,Activity 中會調對應 PhoneWindow 中的分發方法,PhoneWindow 中會調用 DecordView 中的方法, 最終將事件傳遞到 View 中。
猜測事件由 WMS 傳遞到 Window 再到 Activity 再到 Window 這樣多一層 Activity 的原因是,開發者可以在 Activity 中處理事件,不一定非要傳遞到 View
2. Dialog 的 Window 創建過程
同 Activity,實例化 Dialog 對象時創建 PhoneWindow ,show 方法調用時通過 AIDL 調用 WMS 的 addView 方法將 View 添加到屏幕
3. Toast 的 Window 創建過程
Toast 在創建過程中并沒有主動的創建 Window,而是在顯示的時候由系統維護 Toast 的 window,這里也體現了 Window 是一個抽象的概念,最終需要處理的還是 View
Toast 的工作工程需要 TN NMS WMS 三個部分協同完成,IN 也是一個 Binder,NMS 中調用 TN 是遠程訪問,TN 調用 WMS 也是遠程調用
NMS 即 NotificationManagerService
Toast 的工作過程分為兩步
第一步是當前線程中要先生成一個 Binder 類型的 TN 的對象,遠程調用 NMS 中的 enquneue 方法將 TN 傳到 NMS 中,NMS 遠程調用 TN 的 show 方法,TN 中 show 方法運行在當前應用的 Binder 線程池中,通過 Handler 的 post 系列方法將進程切換到主線程,主線程再通過 WindowManager 來調用 WMS 中的方法完成 show 過程 NMS 中調用了 TN 的 show 方法之后,會通過自己內部的 Handler 延時發送一個時間為 Toast 展示時間的消息,NMS 中的 Handler 收到消息之后,再調用 TN 的 hide 方法(遠程調用過程),TN 中的 hide 方法又會通過 WindowManager 遠程調用 WMS 中的 hide 方法,將 Toast 隱藏。完成整個過程。
七、總結
屏幕展示的每一個 window,都需要 window 和 View 兩個相互結合,屏幕中可以有多個 Window。以下所說的 View 都是一個 Window 中包含的根 View
window 的創建以及對 View 的添加,刪除、更新是由 WindowManager 來實現的,而 WindowManager 中對 window 的操作通過 每個 window 對應的 ViewRootImpl 中通過 IPC 遠程請求 IWindowSession 中的方法再調用 WMS 的對應方法將對當前 window 操作的實現到屏幕上。
每一個 Window 都對應一個 ViewRootImpl ,window 通過對應的 ViewRootImpl 來完成對 view 的管理
在屏幕有用戶交互的時候,WMS 又會將事件傳遞到相應界面的 Window,Window 會調用當前界面的對應的 CallBack 來處理事件
WindowManager 是接口,實現類是 WindowManagerImpl,WindowManagerImpl 中又通過 WindowMAnagerGlobal 來完成操作。典型的橋接模式
添加 Window 顯示不出來問題
由于國內對于 ROM 的定制,多種機型會默認禁止應用對懸浮窗的創建,所以如果是沒有顯示,檢查是否關閉了應用的權限。
安卓 6.0 添加了對權限的開關設置,懸浮窗權限默認是關閉的 一些國內定制的 Rom 6.0 之前就可以設置權限的開關,懸浮窗權限默認關閉
問題解決
mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
將 type 設置為 TYPE_TOAST , 源碼中對 TYPE_TOAST 是沒有任何限制的。
在國內定制的 Rom 上,只有少數機型會在設置 TYPE_TOAST 的時候,View 的監聽事件不能獲取,顯示都是可以的。
關于如何理解Android中Window的管理就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。