第8章 理解Window和WindowManager

8.1 Window和WindowManager

(1)Window是抽象類,具體實現是PhoneWindow,通過WindowManager就可以建立Window。WindowManager是外界訪問Window的入口,但是Window的具體實現是在WindowManagerService中,WindowManager和WindowManagerService的互動是一個IPC過程。所有的檢視例如Activity、Dialog、Toast都是附加在Window上的。
(2)通過WindowManager新增View的過程:將一個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引數解析:
FLAG_NOT_FOCUSABLE:表示window不需要獲取焦點,也不需要接收各種輸入事件。此標記會同時啟用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點的window;
FLAG_NOT_TOUCH_MODAL:在此模式下,系統會將window區域外的單擊事件傳遞給底層的window,當前window區域內的單擊事件則自己處理,一般都需要開啟這個標記;
FLAG_SHOW_WHEN_LOCKED:開啟此模式可以讓Window顯示在鎖屏的介面上。 [奇怪的是我刪除這個標記還是在鎖屏看到了新增的元件orz]

type引數表示window的型別,window共有三種類型:應用window,子window和系統window。應用window對應著一個Activity,子window不能獨立存在,需要附屬在特定的父window之上,比如Dialog就是子window。系統window是需要宣告許可權才能建立的window,比如Toast和系統狀態列這些都是系統window,需要宣告的許可權是<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
(3)window是分層的,每個window都對應著z-ordered,層級大的會覆蓋在層級小的上面,應用window的層級範圍是1~99,子window的層級範圍是1000~1999,系統window的層級範圍是2000~2999
[注意,應用window的層級範圍並不是1~999喲]
(4)WindowManager繼承自ViewManager,常用的只有三個方法:addViewupdateViewremoveView

8.2 Window的內部機制

(1)Window是一個抽象的概念,不是實際存在的,它也是以View的形式存在。在實際使用中無法直接訪問Window,只能通過WindowManager才能訪問Window。每個Window都對應著一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯絡。
(2)Window的新增、刪除和更新過程都是IPC過程,以Window的新增為例,WindowManager的實現類對於addViewupdateViewremoveView方法都是委託給WindowManagerGlobal類,該類儲存了很多資料列表,例如所有window對應的view集合mViews、所有window對應的ViewRootImpl的集合mRoots等,之後新增操作交給了ViewRootImpl來處理,接著會通過WindowSession來完成Window的新增過程,這個過程是一個IPC呼叫,因為最終是通過WindowManagerService來完成window的新增的。

8.3 Window的建立過程

(1)Activity的window建立過程
1.Activity的啟動過程很複雜,最終會由ActivityThread中的performLaunchActivity來完成整個啟動過程,在這個方法內部會通過類載入器建立Activity的例項物件,並呼叫它的attach方法為其關聯執行過程中所依賴的一系列上下文環境變數;
2.Activity實現了Window的Callback介面,當window接收到外界的狀態變化時就會回撥Activity的方法,例如onAttachedToWindowonDetachedFromWindowdispatchTouchEvent等;
3.Activity的Window是由PolicyManager來建立的,它的真正實現是Policy類,它會新建一個PhoneWindow物件,Activity的setContentView的實現是由PhoneWindow來實現的;
4.Activity的頂級View是DecorView,它本質上是一個FrameLayout。如果沒有DecorView,那麼PhoneWindow會先建立一個DecorView,然後載入具體的佈局檔案並將view新增到DecorView的mContentParent中,最後就是回撥Activity的onContentChanged通知Activity檢視已經發生了變化;
5.還有一個步驟是讓WindowManager能夠識別DecorView,在ActivityThread呼叫handleResumeActivity方法時,首先會呼叫Activity的onResume方法,然後會呼叫makeVisible方法,這個方法中DecorView真正地完成了新增和顯示過程。

ViewManager vm = getWindowManager();
vm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;

(2)Dialog的Window建立過程
1.過程與Activity的Window建立過程類似,普通的Dialog的有一個特別之處,即它必須採用Activity的Context,如果採用Application的Context會報錯。原因是Application沒有應用token,應用token一般是Activity擁有的。[service貌似也有token?]

(3)Toast的Window建立過程
1.Toast屬於系統Window,它內部的檢視由兩種方式指定:一種是系統預設的演示;另一種是通過setView方法來指定一個自定義的View。
2.Toast具有定時取消功能,所以系統採用了Handler。Toast的顯示和隱藏是IPC過程,都需要NotificationManagerService來實現。在Toast和NMS進行IPC過程時,NMS會跨程序回撥Toast中的TN類中的方法,TN類是一個Binder類,執行在Binder執行緒池中,所以需要通過Handler將其切換到當前傳送Toast請求所在的執行緒,所以Toast無法在沒有Looper的執行緒中彈出。
3.對於非系統應用來說,mToastQueue最多能同時存在50ToastRecord,這樣做是為了防止DOS(Denial of Service,拒絕服務)。因為如果某個應用彈出太多的Toast會導致其他應用沒有機會彈出Toast。

其他學習資料
1.Android應用開發之(WindowManager類使用)

OK,本章結束,謝謝閱讀。