溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android中Window的管理深入講解

發(fā)布時間:2020-09-22 01:17:08 來源:腳本之家 閱讀:186 作者:renxuelong 欄目:移動開發(fā)

一、理解 Android 的 Window

Window 表示一個窗口的概念,是一個抽象的概念,每一個 Window 都對應(yīng)一個 View 和一個 ViewRootImpl,Window 和 View 通過 ViewRootImpl 來建立聯(lián)系,因此 Window 并不是實際存在的,它是以 View 的形式存在。

Android 中的每個窗口 View 都有一個對應(yīng)的 Window,例如 Activity、Dialog,在他們初始化的時候就會為其創(chuàng)建對應(yīng)的PhoneWindow 并賦值到其內(nèi)部的一個引用

window 的層級

WindowLayoutParams.setType 設(shè)置

每個 window 都有其對應(yīng)的層級,應(yīng)用 window 在 1-99,子 window 在 1000-1999,系統(tǒng) window 在 2000-2999 ,層級高的會覆蓋層級低的

子 window 必須依賴于父 window 存在,例如 Dialog 必須在 Activity 中彈出,Dialog 中的 window 為子 window ,Activity 中的 window 為父 window

顯示系統(tǒng)級別的 window 需要權(quán)限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

WindowLayoutparams 的 flags

FLAG_NOT_FOUCSABLE window 不需要獲取焦點,也不需要接收各種輸入事件,回同時啟用 FLAG_NOT_TOUCH_MODAL
FLAG_NOT_TOUCH_MODAL 系統(tǒng)會將當前 Window 區(qū)域外的單擊事件傳遞給底層的 Window,在當前 Window 區(qū)域內(nèi)的事件則自己處理

FLAG_SHOW_WHEN_LOCKED 開啟此模式讓 window 顯示在鎖屏界面上

二、理解 Android 中的 WindowManager

Android 中對 Window 的管理都是通過 WindowManager 來完成的,創(chuàng)建 PhoneWindow 之后還會為該 Window 對象設(shè)置 WindowManager ,WindowManager 是一個接口繼承 ViewManager 接口,從這里也能看出對 Window 的操作其實就是對 View 的操作,WindowManager 的實現(xiàn)類是 WindowMangerImpl ,WindowMangerImpl 通過 new 創(chuàng)建。

三、Window 與 WindowManagerImpl 的關(guān)聯(lián)

通過 ContextImpl 的 getSystemService 可以得到 WindowManagerImpl 實例,同一 ContextImpl 得到的是同一個WindowManagerImpl對象,得到 WindowMangerImpl 之后,調(diào)用 Window 的 setWindowManager 方法建立 Window 與 WindowManagerImpl 之間的聯(lián)系。

setWindowManager 中主要完成在 WindowManagerImpl 實例的基礎(chǔ)上重新創(chuàng)建一個與當前 Window 綁定的 WindowManagerImpl,并為 Window 中的屬性 mWindowManager 賦值

也就是說在 Java 層上 Window 與 WindowManager 建立了第一步聯(lián)系,并將 Activity、Dialog 等中的 WindowManager 賦值為新的 WindowManagerImpl 對象。

注意:這里是使用單例的 WindowManagerImpl ,結(jié)合不同的 Window ,最后構(gòu)建了與 Window 有關(guān)聯(lián)的非單例的 WindowManagerImpl 對象

四、對  Window 的操作

1. 添加操作 WindowManagerImpl.addView,注意,是添加一個新的 Window ,不是對一個 Window 中的 view 做操作

Android 中每顯示一個窗口,其實就是將 View 顯示到屏幕的過程,如果我們自定義一個要顯示的布局,拿到 View 對象,這時候只要調(diào)用 WindowManagerImpl 對象的 addView 方法就行了,通過 ContextImpl 的 getSystemService 可以得到 WindowManagerImpl 實例

WindowManagerImpl 對象,在 WindowManagerImpl 中存在一個單例存在的 WindowManagerGlobal 對象,在 WindowManagerImpl 的各個方法中,將任務(wù)的執(zhí)行過程傳遞到了 WindowManagerGlobal 中,在傳遞過程中除了將 View、LayoutParams 傳遞,還將 WindowManagerImpl 中關(guān)聯(lián)的 window 對象也一起傳遞

WindowManagerGlobal 的 addView 方法

WindowMAnagerGlobal 中判斷 view、LayoutParams 等參數(shù)合法性,創(chuàng)建 ViewRootImpl ,將 ViewRootImpl、View、LayoutParams 添加到 WindowMAnagerGlobal 中對應(yīng)的 ArayyList 集合中,再調(diào)用 ViewRootImpl 的 setView 方法

ViewRootImpl

繼承自 Handler 類,是作為 native 層和 Java 層 View 系統(tǒng)通信的橋梁

ViewRootImpl 創(chuàng)建時保存了創(chuàng)建其的線程的引用,開發(fā)過程中更新 View 時會判斷當前線程是否是創(chuàng)建 ViewRootImpl 的線程,如果不是會拋出異常。

一般都是在主線程中創(chuàng)建 ViewRootImpl ,所以在子線程更新 UI 會拋出異常,是因為 ViewRootImpl 是 UI 線程中創(chuàng)建的,并不是因為只有 UI 線程才可以更新 UI

在 Activity 的 onResume 之前如果在子線程中修改 UI 是不會拋出異常的,因為在 onResume 之后才創(chuàng)建 ViewRootImpl,這時更新 UI 需要經(jīng)過 ViewRootImpl 來更新,在 onResume 之前 Activity 的屏幕并沒有顯示,修改 UI 操作只是會修改 layout 中的 UI,并不會調(diào)用 ViewRootImpl 的方法顯示到屏幕上。

所以得出結(jié)論,只有 UI 顯示到屏幕上之后,在更新 UI 時就會判斷線程是否為創(chuàng)建 UI 的線程,如果不匹配則拋出異常,在 UI 沒有顯示到屏幕上時更新 UI 是不會進行線程判斷的

ViewRootImpl 的 setView 方法:

  1. setView 方法中會首先調(diào)用 requestLayout() 方法,在這里進行線程判斷,如果線程匹配則調(diào)用 scheduleTraversals() 完成 View 的測量、布局、繪制過程
  2. setView 中接下來遠程調(diào)用 IWindowSession 對象中的 addToDisplay 方法,將 window 等信息傳遞到 NMS 中調(diào)用 NMS 的 addWindow 方法完成最后window 在屏幕上的展示。

IWindowSession WindowManagerService

這里是將 View 顯示到屏幕上的關(guān)鍵,是將 View 從應(yīng)用進程傳遞到系統(tǒng)進程然后完成顯示的地方。 ViewRootImpl 中的 IWindowSession 對象是通過 WindowManagerGlobal 的靜態(tài)方法 getWindowSession() 得到的,該方法中首先通過 ServiceManager 通過 Binder 進行 IPC 得到系統(tǒng)服務(wù) WindowsManagerService 在應(yīng)用進程的遠程代理,然后通過 AIDL 通信的方式調(diào)用 WMS 的 openSession() 方法得到 IWindowSession 接口的實現(xiàn)類 Session 類遠程代理對象,Session 類也是一個 Binder 所以可以跨進程傳遞

在 Session 的遠程代理的 addToDisplay 方法中通過 AIDL 調(diào)用 Session 的 addToDisplay 方法將 window 信息傳遞到系統(tǒng)進程,然后調(diào)用 AMS 的 addWindow 方法,AMS 最后將 Window 中的 View 顯示到屏幕上

2. 刪除操作,是刪除一個屏幕上已有的 Window

WindowManagerImpl.removeView 操作中,先通過 findViewLocked 查找要刪除的 View,再通過 View 找到對應(yīng)的 Window 的 ViewRootImpl ,將 View、LayoutParams、ViewRootImpl 從相對應(yīng)的 ArrayList 中刪除,再通過 IPC 調(diào)用 Session 的 remove 其中調(diào)用 WMS 的 removeWindow 方法,在屏幕上移除該 Window 對應(yīng)的 View

3. 更新操作

WindowManagerImpl.updateViewLayout ,為 view 設(shè)置新的 LayoutParams ,通過 findViewLocked 找到對應(yīng) ViewRootImpl,刪除 LayoutParams 集合中舊的 LayoutParams,在集合原位置加入 新的 LayoutParams,調(diào)用 ViewRootImpl 的 setLayoutParams 完成 View 的重新測量,布局,繪制,最后通過 IPC 調(diào)用 Session 再調(diào)用 WMS 完成 window 的更新。

4. 添加 Window 代碼

自定義的 Window 在創(chuàng)建過程中并沒有主動的創(chuàng)建 Window,而是在顯示的時候由系統(tǒng)維護,這里也體現(xiàn)了 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 可以是新創(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)先級
  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)級別的 Window 也就是優(yōu)先級超過 1999 的,需要聲明權(quán)限**
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

五、用戶觸摸屏幕事件處理流程

WMS 將事件 IPC 傳遞到 Window,Window 中調(diào)用其內(nèi)部的 CallBack 對象也就是 Activity 或者 Dialog 對象或者的方法。最終將事件傳遞到 View,再通過 ViewRootImpl 將響應(yīng)以后的 View 和對應(yīng) window IPC 提交到 WMS 完成響應(yīng)后的展示。

六、常見 Window 的創(chuàng)建

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

Activity 啟動流程中,Activity 的 attach 方法中為 window 賦值為一個新的 PhoneWindow ,setContentView 將 layout 傳遞到 PhoneWindow 中,PhoneWindow 中通過 LayoutInflater 的 inflate 方法加載布局 View,并添加到 PhoneWindow 內(nèi)部的 DecorView 中,在 onResume 的時候,調(diào)用 WindowManager 的 addView 方法將 Window 中的 DecorView 通過 WMS 顯示到屏幕上

Activity 在創(chuàng)建 Window 的時候,實現(xiàn)了 Window 的 Callback 接口中的方法,在 Window 收到觸摸時,則會回調(diào) Callback 中的方法將事件傳遞到 Activity 中,Activity 中會調(diào)對應(yīng) PhoneWindow 中的分發(fā)方法,PhoneWindow 中會調(diào)用 DecordView 中的方法, 最終將事件傳遞到 View 中。

猜測事件由 WMS 傳遞到 Window 再到 Activity 再到 Window 這樣多一層 Activity 的原因是,開發(fā)者可以在 Activity 中處理事件,不一定非要傳遞到 View

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

同 Activity,實例化 Dialog 對象時創(chuàng)建 PhoneWindow ,show 方法調(diào)用時通過 AIDL 調(diào)用 WMS 的 addView 方法將 View 添加到屏幕

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

Toast 在創(chuàng)建過程中并沒有主動的創(chuàng)建 Window,而是在顯示的時候由系統(tǒng)維護 Toast 的 window,這里也體現(xiàn)了 Window 是一個抽象的概念,最終需要處理的還是 View

Toast 的工作工程需要 TN NMS WMS 三個部分協(xié)同完成,IN 也是一個 Binder,NMS 中調(diào)用 TN 是遠程訪問,TN 調(diào)用 WMS 也是遠程調(diào)用

NMS 即 NotificationManagerService

Toast 的工作過程分為兩步

  • 第一步是當前線程中要先生成一個 Binder 類型的 TN 的對象,遠程調(diào)用 NMS 中的 enquneue 方法將 TN 傳到 NMS 中,NMS 遠程調(diào)用 TN 的 show 方法,TN 中  show 方法運行在當前應(yīng)用的 Binder 線程池中,通過 Handler 的 post 系列方法將進程切換到主線程,主線程再通過 WindowManager 來調(diào)用 WMS 中的方法完成 show 過程
  • NMS 中調(diào)用了 TN 的 show 方法之后,會通過自己內(nèi)部的 Handler 延時發(fā)送一個時間為 Toast 展示時間的消息,NMS 中的 Handler 收到消息之后,再調(diào)用 TN 的 hide 方法(遠程調(diào)用過程),TN 中的 hide 方法又會通過 WindowManager 遠程調(diào)用 WMS 中的 hide 方法,將 Toast 隱藏。完成整個過程。

七、總結(jié)

屏幕展示的每一個 window,都需要 window 和 View 兩個相互結(jié)合,屏幕中可以有多個 Window。以下所說的 View 都是一個 Window 中包含的根 View

window 的創(chuàng)建以及對 View 的添加,刪除、更新是由 WindowManager 來實現(xiàn)的,而 WindowManager 中對 window 的操作通過 每個 window 對應(yīng)的 ViewRootImpl 中通過  IPC 遠程請求 IWindowSession 中的方法再調(diào)用 WMS 的對應(yīng)方法將對當前 window 操作的實現(xiàn)到屏幕上。

每一個 Window 都對應(yīng)一個 ViewRootImpl ,window 通過對應(yīng)的 ViewRootImpl 來完成對 view 的管理

在屏幕有用戶交互的時候,WMS 又會將事件傳遞到相應(yīng)界面的 Window,Window 會調(diào)用當前界面的對應(yīng)的 CallBack 來處理事件

WindowManager 是接口,實現(xiàn)類是 WindowManagerImpl,WindowManagerImpl 中又通過 WindowMAnagerGlobal 來完成操作。典型的橋接模式

添加 Window 顯示不出來問題

由于國內(nèi)對于 ROM 的定制,多種機型會默認禁止應(yīng)用對懸浮窗的創(chuàng)建,所以如果是沒有顯示,檢查是否關(guān)閉了應(yīng)用的權(quán)限。

  • 安卓 6.0 添加了對權(quán)限的開關(guān)設(shè)置,懸浮窗權(quán)限默認是關(guān)閉的
  • 一些國內(nèi)定制的 Rom 6.0 之前就可以設(shè)置權(quán)限的開關(guān),懸浮窗權(quán)限默認關(guān)閉

問題解決

mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;

將 type 設(shè)置為 TYPE_TOAST , 源碼中對 TYPE_TOAST 是沒有任何限制的。

在國內(nèi)定制的 Rom 上,只有少數(shù)機型會在設(shè)置 TYPE_TOAST 的時候,View 的監(jiān)聽事件不能獲取,顯示都是可以的。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對億速云的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI