學習筆記|《Android開發藝術探索》第八章

理解Window和WindowManager

Window是一個抽象類,它的具體實現是PhoneWindow。WindowManager是外界訪問Window的入口,Window的具體實現位於WindowManagerService中,WindowManager和WindowManagerService的交互是一個IPC過程。Android中所有的視圖都是通過Window來呈現的,不管是Activity、Dialog還是Toast,它們的視圖實際上都是附加在Window上的,因此Window實際是View的直接管理者。

8.1 Window和WindowManager

爲了分析Window的工作機制,先通過代碼瞭解如何使用WindowManager添加一個Window,下面一段代碼將一個Button添加到屏幕座標爲(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參數,在後面分別配置了
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參數表示Window的屬性,以下列舉常用的選項:

  • FLAG_NOT_FOCUSABLE:表示Window不需要獲取焦點,也不需要接收各種輸入事件,此標記會同時啓動FLAG_NOT_TOUCH_MODEL,最終事件會傳遞給下層的具有焦點的Window
  • FLAG_NOT_TOUCH_MODAL:在此模式下,系統會將當前Window區域以外的單擊事件傳遞給底層的Window,當前Window區域以內的單擊事件則自己處理。這個標記很重要,一般來說都需要開啓此標記,否則其他Window將無法收到單擊事件。
  • FLAG_SHOW_WHEN_LOCKED:開啓此模式可以讓顯示在鎖屏的界面

Type參數表示Window的類型,Window有三種類型,分別是應用Window、子Window和系統Window。應用類Window對應着一個Activity。子Window不能單獨存在,它需要附屬在特定的父Window之中,比如常見的一些Dialog就是一個子Window。系統Window是需要聲明權限才能創建的Window,比如Toast和系統狀態欄這些都是系統Window。

Window是分層的,每個Window都有對應的z-ordered,層級最大的會覆蓋在層級小的Window上面,這和HTML中的z-index的概念是完全一致的。在三類Window中,應用Window的層級範圍是199,子Window的層級範圍是10001999,系統Window的層級範圍是2000~2999,這些層級屬性範圍對應着WindowManager.LayoutParams的type參數。

如果採用TYPE_SYSTEM_ERROR,只需要爲type參數指定這個層級即可:

mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR

同時聲明權限:

<uses-permissionandroid:name=“android.permission.SYSTEM_ALERT_WINDOW” />

WindowManager所提供的功能很簡單,常用的只有三個方法,即添加View、更新View和刪除View,這三個方法定義在ViewManager中,而WindowManager繼承了ViewManager。

8.2 Window的內部機制

Window是一個抽象的概念,並不是實際存在的,它是以View的形式存在,每一個Window都對應着一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯繫。在實際使用中無法直接訪問Window,對Window的訪問必須通過WindowManager。

8.2.1 Window的添加過程

Window的添加過程需要通過WindowManager的addView來實現,WindowManager是一個接口,它的真正實現是WindowManagerImpl類。WindowManager的實現類對於addView、updateView和removeView方法都是委託給WindowManagerGlobal類。

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

  1. 檢查參數是否合法,如果是子Window那麼還需要調整一些佈局參數
  2. 創建ViewRootImpl並將View添加到列表中
  3. 通過ViewRootImpl來更新界面並完成Window的添加過程

8.2.2 Window的刪除過程

和添加過程一樣,都是先通過WindowManagerImpl後,再進一步通過WindowManagerGlobal來實現的。
真正刪除View的邏輯在dispatchDetachedFromWindow方法的內部實現。dispatchDetachedFromWindow方法主要做四件事:

  1. 垃圾回收的工作,比如清除數據和消息,移除回調。
  2. 通過Session的remove方法刪除Window,mWindowSession.remove(mWindow),這同樣是一個IP C過程,最終會調用WindowManagerService的removeWindow方法
  3. 調用View的dispatchDetachedFromWindow方法,在內部調用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。
  4. 調用WindowManagerGlobal的doRemoveView方法刷新數據,包括mRoots、mParams以及mDyingViews,需要將當前Window所關聯的這三類對象從列表中刪除。

8.2.3 Window的更新過程

首先需要更新View的LayoutParams並替換掉老的LayoutParams,接着再更新ViewRootImpl中的LayoutParams,這一步是通過ViewRootImpl的setLayoutParams方法來實現的。在ViewRootImpl中會通過scheduleTrversals方法來對View重新佈局,包括測量、佈局、重繪三個過程。除了View本身的重繪以外,ViewRootImpl還會通過WindowSession來更新Window的視圖,這個過程最終是由WindowManagerService的relayoutWindow()來具體實現的,同樣是一個IPC過程。

Window的創建過程

8.3.1 Activity的Window創建過程

1、Activity的啓動過程很複雜,最終會由ActivityThread中的performLaunchActivity()來完成整個啓動過程,在這個方法內部會通過類加載器創建Activity的實例對象,並調用其attach方法爲其關聯運行過程中所依賴的一系列上下文環境變量。

2、Activity實現了Window的Callback接口,當Window接收到外界的狀態變化時就會調用Activity的方法,例如onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent等。

3、Activity的Window是由PolicyManager來創建的,它的真正實現是Policy類,它會新建一個PhoneWindow對象,Activity的setContentView的實現是由PhoneWindow來實現的。
PhoneWindow方法大致遵循如下幾個步驟:

  1. 如果沒有DecorView,那麼就創建它
  2. 將View添加到DecorView的mContentParent中
  3. 回調Activity的onCreateChanged方法通知Activity視圖已經發生改變

8.3.2 Dialog的Window創建過程

Dialog的Window的創建過程和Activity類似,有如下步驟:

  1. 創建Window:Diolog中Window的創建同樣是通過PolicyManager的makeNewWindow方法來完成的,創建後的對象實際上就是PhoneWindow。
  2. 初始化DecorView並將Dialog的視圖添加到DecorView中
  3. 將DecorView添加到Window中並顯示:普通的Dialog有一個特殊之處,就是必須採用Activity的Context,如果採用Application的Context,那麼就會報錯。應用token只有Activity擁有,所以這裏只需要Activity作爲Context來顯示對話框即可。

8.3.3 Toast的Window創建過程

在Toast的內部有兩類IPC過程,第一類是Toast訪問NotificationManagerService,第二類是NotificationManagerService回調Toast裏的TN接口。

Toast屬於系統Window,它內部的視圖由兩種方式指定:一種是系統默認的演示,另一種是通過setView方法來指定一個自定義的View

Toast具有定時取消功能,所以系統採用了Handler。Toast的顯示和隱藏是IPC過程,都需要NotificationManagerService(NMS)來實現,在Toast和NMS進行IPC過程時,NMS會跨進程回調Toast中的TN類中的方法,TN類是一個Binder類,運行在Binder線程池中,所以需要通過Handler將其切換到當前發送Toast請求所在的線程,所以Toast無法在沒有Looper的線程中彈出。

對於非系統應用來說,mToastQueue最多能同時存在50個ToastRecord,這樣做是爲了防止DOS(Denial of Service,拒絕服務)。因爲如果某個應用彈出太多的Toast會導致其他應用沒有機會彈出Toast。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章