天天看點

Android 帶你徹底了解 Window 和 WindowManagerWindowManagerServiceWindow 分類WindowManager 使用WindowManager 的内部機制Window 的建立過程總結

有時候我們需要在桌面上顯示一個類似懸浮窗的東西,這種效果就需要用 Window 來實作,Window 是一個抽象類,表示一個視窗,它的具體實作類是 PhoneWindow,實作位于 WindowManagerService 中。相信看到 WindowManagerService 你會有點眼熟,剛接觸 Android 時幾乎所有人都看到過這樣一張圖:

Android 帶你徹底了解 Window 和 WindowManagerWindowManagerServiceWindow 分類WindowManager 使用WindowManager 的内部機制Window 的建立過程總結

WindowManagerService

WindowManagerService 就是位于 Framework 層的視窗管理服務,它的職責就是管理系統中的所有視窗。視窗的本質是什麼呢?其實就是一塊顯示區域,在 Android 中就是繪制的畫布:Surface,當一塊 Surface 顯示在螢幕上時,就是使用者所看到的視窗了。WindowManagerService 添加一個視窗的過程,其實就是 WindowManagerService 為其配置設定一塊 Surface 的過程,一塊塊的 Surface 在 WindowManagerService 的管理下有序的排列在螢幕上,Android 才得以呈現出多姿多彩的界面。于是根據對 Surface 的操作類型可以将 Android 的顯示系統分為三個層次,如下圖:

Android 帶你徹底了解 Window 和 WindowManagerWindowManagerServiceWindow 分類WindowManager 使用WindowManager 的内部機制Window 的建立過程總結

一般的開發過程中,我們操作的是 UI 架構層,對 Window 的操作通過 WindowManager 即可完成,而 WindowManagerService 作為系統級服務運作在一個單獨的程序,是以 WindowManager 和 WindowManagerService 的互動是一個 IPC 過程。

Window 分類

Window 有三種類型,分别是應用 Window、子 Window 和系統 Window。應用類 Window 對應一個 Acitivity,子 Window 不能單獨存在,需要依附在特定的父 Window 中,比如常見的一些 Dialog 就是一個子 Window。系統 Window是需要聲明權限才能建立的 Window,比如 Toast 和系統狀态欄都是系統 Window。

Window 是分層的,每個 Window 都有對應的 z-ordered,層級大的會覆寫在層級小的 Window 上面,這和 HTML 中的 z-index 概念是完全一緻的。在三種 Window 中,應用 Window 層級範圍是 1~99,子 Window 層級範圍是 1000~1999,系統 Window 層級範圍是 2000~2999,我們可以用一個表格來直覺的表示:

Window 層級
應用 Window 1~99
子 Window 1000~1999
系統 Window 2000~2999

這些層級範圍對應着 WindowManager.LayoutParams 的 type 參數,如果想要 Window 位于所有 Window 的最頂層,那麼采用較大的層級即可,很顯然系統 Window 的層級是最大的,當我們采用系統層級時,需要聲明權限。

WindowManager 使用

我們對 Window 的操作是通過 WindowManager 來完成的,WindowManager 是一個接口,它繼承自隻有三個方法的 ViewManager 接口:

public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}           
  • 1
  • 2
  • 3
  • 4
  • 5

這三個方法其實就是 WindowManager 對外提供的主要功能,即添加 View、更新 View 和删除 View。接下來來看一個通過 WindowManager 添加 Window 的例子,代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button floatingButton = new Button(this);
        floatingButton.setText("button");
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                , ,
                PixelFormat.TRANSPARENT
        );
        // flag 設定 Window 屬性
        layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        // type 設定 Window 類别(層級)
        layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
        layoutParams.gravity = Gravity.CENTER;
        WindowManager windowManager = getWindowManager();
        windowManager.addView(floatingButton, layoutParams);

    }
}
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

代碼中并沒有調用 Activity 的 setContentView 方法,而是直接通過 WindowManager 添加 Window,其中設定為系統 Window,是以應該添權重限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
           
  • 1
  • 2

效果如下:

Android 帶你徹底了解 Window 和 WindowManagerWindowManagerServiceWindow 分類WindowManager 使用WindowManager 的内部機制Window 的建立過程總結

第二個界面是鎖屏界面,由于按鈕是處于較大層級的系統 Window 中的,是以可以看到 button。

WindowManager 的内部機制

在實際使用中無法直接通路 Window,對 Window 的通路必須通過 WindowManager。WindowManager 提供的三個接口方法 addView、updateViewLayout 以及 removeView 都是針對 View 的,這說明 View 才是 Window 存在的實體,上面例子實作了 Window 的添加,WindowManager 是一個接口,它的真正實作是 WindowManagerImpl 類:

@Override
        public void addView(View view, ViewGroup.LayoutParams params){
            mGlobal.addView(view, params, mDisplay, mParentWindow);
        }

        @Override
        public void updateViewLayout(View view, ViewGroup.LayoutParams params){
            mGlobal.updateViewLayout(view, params);
        }

        @Override
        public void removeView(View view){
            mGlobal.removeView(view, false);
        }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以看到,WindowManagerImpl 并沒有直接實作 Window 的三大操作,而是交給了 WindowManagerGlobal 來處理,下面以 addView 為例,分析一下 WindowManagerGlobal 中的實作過程:

1、檢查參數合法性,如果是子 Window 做适當調整

if(view == null){
   throw new IllegalArgumentException("view must not be null");
}

if(display == null){
   throw new IllegalArgumentException("display must not be null");
}

if(!(params instanceof WindowManager.LayoutParams)){
   throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if(parentWindow != null){
   parentWindow.adjustLayoutParamsForSubWindow(wparams);
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2、建立 ViewRootImpl 并将 View 添加到集合中

在 WindowManagerGlobal 内部有如下幾個集合比較重要:

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
           
  • 1
  • 2
  • 3
  • 4
  • 5

其中 mViews 存儲的是所有 Window 所對應的 View,mRoots 存儲的是所有 Window 所對應的 ViewRootImpl,mParams 存儲的是所有 Window 所對應的布局參數,mDyingViews 存儲了那些正在被删除的 View 對象,或者說是那些已經調用了 removeView 方法但是操作删除還未完成的 Window 對象,可以通過表格直覺的表示:

集合 存儲内容
mViews Window 所對應的 View
mRoots Window 所對應的 ViewRootImpl
mParams Window 所對應的布局參數
mDyingViews 正在被删除的 View 對象

addView 操作時會将相關對象添加到對應集合中:

root = new ViewRootImpl(view.getContext(),display);
view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3、通過 ViewRootImpl 來更新界面并完成 Window 的添加過程

在學習 View 的工作原理時,我們知道 View 的繪制過程是由 ViewRootImpl 來完成的,這裡當然也不例外,具體是通過 ViewRootImpl 的 setView 方法來實作的。在 setView 内部會通過 requestLayout 來完成異步重新整理請求,如下:

public void requestLayout(){
   if(!mHandingLayoutInLayoutRequest){
       checkThread();
       mLayoutRequested = true;
       scheduleTraversals();
   }
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可以看到 scheduleTraversals 方法是 View 繪制的入口,繼續檢視它的實作:

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), 
          mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);           
  • 1
  • 2

mWindowSession 的類型是 IWindowSession,它是一個 Binder 對象,真正的實作類是 Session,這也就是之前提到的 IPC 調用的位置。在 Session 内部會通過 WindowManagerService 來實作 Window 的添加,代碼如下:

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams, attrs, int viewVisibility, 
                  int displayId, Rect outContentInsets, InputChannel outInputChannel){
   return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outInputChannel);
}           
  • 1
  • 2
  • 3
  • 4

終于,Window 的添加請求移交給 WindowManagerService 手上了,在 WindowManagerService 内部會為每一個應用保留一個單獨的 Session,具體 Window 在 WindowManagerService 内部是怎麼添加的,就不對其進一步的分析,因為到此為止我們對 Window 的添加這一從應用層到 Framework 的流程已經清楚了,下面通過圖示總結一下:

Android 帶你徹底了解 Window 和 WindowManagerWindowManagerServiceWindow 分類WindowManager 使用WindowManager 的内部機制Window 的建立過程總結

了解了 Window 的添加過程,Window 的删除過程和更新過程都是類似的,也就容易了解了,它們最終都會通過一個 IPC 過程将操作移交給 WindowManagerService 這個位于 Framework 層的視窗管理服務來處理。

Window 的建立過程

View 是 Android 中的視圖的呈現方式,但是 View 不能單獨存在,它必須附着在 Window 這個抽象的概念上面,是以有視圖的地方就有 Window。哪些地方有視圖呢?Android 可以提供視圖的地方有 Activity、Dialog、Toast,除此之外,還有一些依托 Window 而實作的視圖,比如 PopUpWindow(自定義彈出視窗)、菜單,它們也是視圖,有視圖的地方就有 Window,是以 Activity、Dialog、Toast 等視圖都對應着一個 Window。這也是面試中常問到的一個知識點:一個應用中有多少個 Window?下面分别分析 Activity、Dialog以及 Toast 的 Window 建立過程。

1、 Activity 的 Window 建立過程

在了解了 Window 的概念及意義後,我們自然就清楚 Activity 的 Window 建立時機,Window 本質就是一塊顯示區域,是以關于 Activity 的 Window 建立應該發生在 Activity 的啟動過程,Activity 的啟動過程很複雜,最終會由 ActivityThread 中的 performLaunchActivity() 來完成整個啟動過程,在這個方法内部會通過類加載器建立 Activity 的執行個體對象,并調用其 attach 方法為其關聯運作過程中所依賴的一系列上下文環境變量。

Activity 的 Window 建立就發生在 attach 方法裡,系統會建立 Activity 所屬的 Window 對象并為其設定回調接口,代碼如下:

mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...           
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到, Window 對象的建立是通過 PolicyManager 的 makeNewWindow 方法實作的,由于 Activity 實作了 Window 的 Callback 接口,是以當 Window 接受到外界的狀态改變時就會回調 Activity 的方法。Callback 接口中的方法很多,有幾個是我們非常熟悉的,如 onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent 等等。

再回到 Window 的建立,可以看到 Activity 的 Window 是通過 PolicyManager 的一個工廠方法來建立的,但是在 PolicyManager 的實際調用中,PolicyManager 的真正實作是 Policy 類,Policy 類中的 makeNewWindow 方法的實作如下:

public Window  makeNewWindow(Context context){
   return new PhoneWindow(context);
}           
  • 1
  • 2
  • 3

可以看出,Window 的具體實作類的确是 PhoneWindow。到這裡 Window 以及建立完成了,下面分析 Activity 的視圖是怎麼附屬到 Window 上的,而 Activity 的視圖由 setContentView 提供,是以從 setContentView 入手,它的源碼如下:

public void setContentView(int layoutResID){
   getWindow().setContentView(layoutResID);
   initWindowDecorActionBar();
}           
  • 1
  • 2
  • 3
  • 4

可以看到,Activity 将具體實作交給了 Window,而 Window 的具體實作是 PhoneWindow,是以隻需要看 PhoneWindow 的相關邏輯即可,它的處理步驟如下:

(1)、如果沒有 DecorView 就建立一個

DecorView 是 Activity 中的頂級 View,是一個 FrameLayout,一般來說它的内部包含标題欄和内容欄,但是這個會随着主題的變化而改變,不管怎麼樣,内容欄是一定存在的,并且有固定的 id:”android.R.id.content”,在 PhoneWindow 中,通過 generateDecor 方法建立 DecorView,通過 generateLayout 初始化主題有關布局。

(2)、将 View 添加到 DecorView 的 mContentParent 中

這一步較為簡單,直接将 Activity 的視圖添加到 DecorView 的 mContentParent 中即可,由此可以了解 Activity 的 setContentView 這個方法的來曆了,為什麼不叫 setView 呢?因為 Activity 的布局檔案隻是被添加到 DecorView 的 mContentParent 中,是以叫 setContentView 更加具體準确。

(3)、回調 Activity 的 onContentChanged 方法通知 Activity 視圖已經發生改變

前面分析到 Activity 實作了 Window 的 Callback 接口,這裡當 Activity 的視圖已經被添加到 DecorView 的 mContentParent 中了,需要通知 Activity,使其友善做相關的處理。

經過上面的三個步驟,DecorView 已經被建立并初始化完畢,Activity 的布局檔案也已經成功添加到了 DecorView 的 mContentParent 中,但是這個時候 DecorView 還沒有被 WindowManager 正式添加到 Window 中。在 ActivityThread 的 handleResumeActivity 方法中,首先會調用 Acitivy 的 onResume 方法,接着會調用 Acitivy 的 makeVisible() 方法,正是在 makeVisible 方法中,DecorView 才真正的完成了顯示過程,到這裡 Activity 的視圖才能被使用者看到,如下:

void makeVisible(){
   if(!mWindowAdded){
      ViewManager wm = getWindowManager();
      wm.addView(mDecor, getWindow().getAttributes());
      mWindowAdded = true;
   }
   mDecor.setVisibility(View.VISIBLE);
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2、 Dialog 的 Window 建立過程

Dialog 的 Window 的建立過程與 Activity 類似,步驟如下:

(1)、建立 Window

Dialog 中 Window 同樣是通過 PolicyManager 的 makeNewWindow 方法來完成的,建立後的對象也是 PhoneWindow。

(2)、初始化 DecorView 并将 Dialog 的視圖添加到 DecorView 中

這個過程也和 Activity 類似,都是通過 Window 去添加指定布局檔案:

public void setContentView(int layoutResID){
   mWindow.setContentView(layoutResID);
}           
  • 1
  • 2
  • 3

(3)、将 DecorView 添加到 Window 中并顯示

在 Dialog 的 show 方法中,會通過 WindowManager 将 DecorView 添加到 Window 中,如下:

mWindowManager.addView(mDecor, );
mShowing = true;           
  • 1
  • 2

從上面三個步驟可以發現,Dialog 的 Window 建立過程和 Activity 建立過程很類似,當 Dialog 關閉時,它會通過 WindowManager 來移除 DecorView。普通的 Dialog 必須采用 Activity 的 Context,如果采用 Application 的 Context 就會報錯。這是因為沒有應用 token 導緻的,而應用 token 一般隻有 Activity 擁有,另外,系統 Window 比較特殊,可以不需要 token。

3、 Toast 的 Window 建立過程

Toast 與 Dialog 不同,它的工作過程稍顯複雜,首先 Toast 也是基于 Window 來實作的,但是由于 Toast 具有定時取消這一功能,是以系統采用了 Handler。在 Toast 内部有兩類 IPC 過程,一是 Toast 通路 NotificationManagerService,第二類是 NotificationManagerService 回調 Toast 裡的 TN 接口。NotificationManagerService 同 WindowManagerService 一樣,都是位于 Framework 層的服務,下面簡稱 NotificationManagerService 為 NMS。

Toast 屬于系統 Window,它内部的視圖可以是系統預設樣式也可以通過 setView 方法自定義 View,不管如何,它們都對應 Toast 的内部成員 mNextView,Toast 提供 show 和 cancel 分别用于顯示和隐藏 Toast,它們内部是一個 IPC 過程,代碼如下:

public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
public void cancel() {
        mTN.hide();

        try {
            getService().cancelToast(mContext.getPackageName(), mTN);
        } catch (RemoteException e) {
            // Empty
        }
    }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以看到,顯示和隐藏 Toast 都需要通過 NMS 來實作,TN 是一個 Binder 類,當 NMS 處理 Toast 的顯示或隐藏請求時會跨程序回調 TN 中的方法。由于 TN 運作在 Binder 線程池中,是以需要通過 Handler 将其切換到目前線程中,這裡的目前線程指的是發送 Toast 請求所在的線程。

代碼在顯示 Toast 中調用了 NMS 的 enqueueToast 方法, enqueueToast 方法内部将 Toast 請求封裝為 ToastRecord 對象并将其添加到一個名為 mToastQueue 的隊列中,對于非系統應用來說,mToastQueue 中最多同時存在 50 個 ToastRecord,用于防止 DOS (Denial of Service 拒絕服務)。

當 ToastRecord 添加到 mToastQueue 中後,NMS 就會通過 showNextToastLocked 方法來順序顯示 Toast,但是 Toast 真正的顯示并不是在 NMS 中完成的,而是由 ToastRecord 的 callback 來完成的:

void showNextToastLocked (){
   ToastRecord record = mToastQueue.get();
   while(record != null){
       if(DBG) 
          Slog.d(TAG,"show pkg=" + record.pkg + "callback=" + record.callback);
       try{
          record.callback.show();
          scheduleTimeoutLocked(record);
          return;
        }

       ...

}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

這個 callback 就是 Toast 中的 TN 對象的遠端 Binder,最終被調用的 TN 中的方法會運作在發起 Toast 請求的應用的 Binder 線程池中,從以上代碼可以看出,Toast 顯示以後,NMS 還調用了 sheduleTimeoutLocked 方法,此方法中首先進行延時,具體的延時時長取決于 Toast 的顯示時長,延遲相應時間後,NMS 會通過 cancelToastLocked 方法來隐藏 Toast 并将它從 mToastQueue 中移除,這時如果 mToastQueue 中還有其他 Toast,那麼 NMS 就繼續顯示其他 Toast。Toast 的隐藏也是通過 ToastRecord 的 callback 來完成的,同樣也是一次 IPC 過程。

從上面的分析,可以知道 NMS 隻是起到了管理 Toast 隊列及其延時的效果,Toast 的顯示和隐藏過程實際上是通過 Toast 的 TN 類來實作的,TN 類的兩個方法 show 和 hide,是被 NMS 以跨程序的方式調用的,是以它們運作在 Binder 線程池中,為了将執行環境切換到 Toast 請求所在的線程,在它們内部使用了 Handler。

Toast 畢竟是要在 Window 中實作的,是以它最終還是要依附于 WindowManager,TN 的 handleShow 中代碼如下:

mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);           
  • 1
  • 2

TN 的 handleHide 方法同樣需要通過 WindowManager 來實作視圖的移除,這裡就不再貼出。

總結

下面讓我們再次認清一些概念:任何 View 都是附屬在一個 Window 上面的,Window 表示一個視窗的概念,也是一個抽象的概念,Window 并不是實際存在的,它是以 View 的形式存在的。WindowManager 是外界也就是我們通路 Window 的入口,Window 的具體實作位于 WindowManagerService 中,WindowManagerService 和 WindowManager 的互動是一個 IPC 過程。

相信讀完本文後,對 Window 會有一個更加清晰的認識,同時能夠深刻了解 Window 和 View 的依賴關系。

參考文章:

《Android 開發藝術探索》

http://blog.csdn.net/luoshengyang/article/details/8462738

http://blog.csdn.net/innost/article/details/47660193

http://www.tuicool.com/articles/MjAjIfU

http://blog.csdn.net/wzy_1988/article/details/43341761

http://blog.csdn.net/luoshengyang/article/details/6689748