一、理解 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_MODAL
FLAG_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 方法:
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 的工作過程分為兩步
七、總結
屏幕展示的每一個 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 的定制,多種機型會默認禁止應用對懸浮窗的創建,所以如果是沒有顯示,檢查是否關閉了應用的權限。
問題解決
mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
將 type 設置為 TYPE_TOAST , 源碼中對 TYPE_TOAST 是沒有任何限制的。
在國內定制的 Rom 上,只有少數機型會在設置 TYPE_TOAST 的時候,View 的監聽事件不能獲取,顯示都是可以的。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。