一、Window和WindowManager
1、爲了分析Window的工作機制,我們先了解一下如何使用WindowManager添加一個Window。
示例代碼:
Button btn = new Button(this);
btn.setText("button");
LayoutParams lp = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,0,0,PixelFromat.TRANSPARENT);
lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_SHOW_WHEN_LOCKED;
lp.gravity = Gravity.LEFT | Gravity.TOP;
lp.x= 100;
lp.y=300;
windowManager.addView(btn,lp);
2、Flags參數表示Window的屬性,常用的如下:
FLAG_NOT_FOCUSABLE:表示window不需要獲取焦點,也不需要接受各種輸入事件,此標記會同時啓用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點的window
FLAG_NOT_TOUCH_MODAL:系統會將當前window區域以外的單擊事件傳遞給底層window,當前window區域內的單擊事件則自己處理
FLAG_SHOW_WHEN_LOCKED:開啓此模式可以讓window顯示在鎖屏的界面上。
3、Type參數表示window類型,window有三種類型,分別應用window、子window、和系統window。
window是分層的,每個window都有對應的z-ordered,層級大的會覆蓋在層級小的上面。應用window的層級範圍是1-99,子window的層級範圍是1000-1999,系統window的層級範圍是2000-2999,這些層級對應這WindowManager.LayoutParams的type參數.如果想讓Window位於所有window的最上層,採用較大的層級即可。
WindowManager所提供的功能很簡單,常用的方法只有三個:
添加View、更新View、刪除View,這三個方法定義在ViewManager中,而WindowManager繼承了ViewManager。
二、window的內部機制
window是一個抽象的概念,每一個window都對應着一個View和一個ViewRootImpl,Window和View通過ViewRootImpl建立聯繫,因此Window並不是實際存在的,它是以View的形式存在
1、window的添加過程
window的添加過程需要通過WindowManager的addView來實現,WindowManager是一個接口,他的真正實現是WindowManagerImpl類。在WindowManagerImpl中Window的三大操作是實現如下
@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);
}
可以發現,WindowManagerImpl並沒有直接實現Window,而是全部交給WindowManagerGlobal來處理,WindowManagerGlobal以工廠形式向外提供自己的實例。
在WindowManagerGlobal中有一段代碼:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance()。
WindowManagerImpl這種工作模式是典型的橋接模式,將所有的操作全部委託給WindowManagerGlobal實現。WindowManagerGlobal的addView方法主要分爲如下幾步:
(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);
}
(2)創建ViewRootImpl並將View添加到列表中
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>();
root = new ViewRootImpl(view.getContext(),display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
(3)通過ViewRootImpl來更新界面並完成Window的添加過程,View的繪製過程是由ViewRootImpl來完成的,在setView內部會通過requestLayout來完成異步刷新請求。scheduleTraversals實際是View繪製的入口:
public void requestLayout(){
if(!mHandlinglayoutInLayoutRequest){
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
2、Window的刪除過程
Window的刪除過程和添加過程一樣,都是先通過WindowManagerImpl,在進一步通過WindowManagerGlobal來實現。
public void removeView(View view,boolean immediate){
if(view == null){
throw new IllegalArgumentException("view must not be null")
}
synchronized(mLock){
int index = findViewLocked(view,true);
View curView = mRoots.get(index).getView();
removeViewLocked(index,immediate);
if(curView == view){
return;
}
throw new IllegalArgumentException("Calling with view"+view+"but the ViewAncestor is attached to"+curView);
}
}
public void removeViewLocked(int index,boolean immediate){
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if(view != null){
InputMethodManager imm = InputMethodManager.getInstance();
if(imm != null){
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if(view != null){
view.assignParent(null);
if(deferred){
mDyingViews.add(view);
}
}
}
removeViewLocked通過ViewRootImpl來完成刪除操作。windowManager提供了兩種刪除接口removeView和removeViewImmediate,分別表示異步刪除和同步刪除
boolean die(boolean immediate){
if(immediate && !mIsInTraversal){
doDie();
return false;
}
if(!mIsDrawing){
destroyHasdwareRenderer();
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
3、Window的更新過程
Window的更新過程要看WindowManagerGlobal的updateViewLayout方法。
代碼如下:
public void updateViewLayout(View view,ViewGroup.LayoutParams params){
if(view == null){
throw new IllegalArgumentException("view must not be null");
}
if(!(params instanceof WindowManager.LayoutParams)){
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized(mLock){
int index = findViewLocked(view,true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index,wparams);
root.setLayoutParams(wparams,false);
}
}
三:Window的創建過程
1、Activity的Window創建過程
activity啓動過程最終由ActivityThread中的performLaunchActivity()來完成整個啓動過程,在這個方法內部會通過類加載器創建Activity的實例對象,並調用attach方法爲其關聯運行過程中所依賴的一系列上下文環境變量。
在activity的attach方法中,系統會創建Activity所屬的Window對象併爲其設置回調接口,Window對象的創建是通過PolicyManager的makeNewWindow方法實現的。由於Activity實現了Window的Callback接口,因此當Window接收到外界的狀態改變時就會回調Activity的方法。
Activity的視圖附屬在window流程:
Activity將具體實現交給Window處理,而Window的具體實現是PhoneWindow,PhoneWindow的setContentView方法大致遵循以下幾個步驟:
(1)如果沒有DecorView,就創建他
DecorView是FrameLayout,是activity中頂級View,一般內部包含標題欄和內容欄
(2)將View添加到DecorView的mContentParent中
(3)回調Activity的onContentChanged方法通知Activity視圖已經發生改變
2、Dialog的Window創建過程
Dialog的Window的創建過程和Activity類似。有以下幾個步驟:
(1)創建Window
Dialog中Window的創建同樣是通過PolicyManager的makeNewWindow方法來完成的
(2)初始化DecorView並將Dialog的視圖添加到DecorView中
(3)將DecorView添加到Window中並顯示
3、Toast的Window創建過程
Toast和Dialog不同,他的工作過程比較複雜。Toast也是基於Window來實現的,但是Toast具有定時取消的功能,所以系統採用了Handler。在Toast的內部有兩類IPC過程,第一類是Toast訪問NotificationManagerService,第二類是NotificationManagerService回調Toast裏的TN接口。
Toast屬於系統Window,它內部的視圖由兩種方式指定,一種是系統默認的樣式,另一種是通過setView方法來指定一個自定義View。Toast提供了show和cancel分別用於顯示和隱藏Toast。
代碼如下:
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){
}
}
public void cancel(){
mTN.hide();
try{
getService().cancelToast(mContext.getPackageName(),mTN);
}catch(RemoteException e){
}
}
顯示和隱藏Toast都需要通過NMS來實現,由於NMS運行在系統的進程中,所以只能通過遠程調用的方式來顯示和隱藏Toast。需要注意的是TN這個類是一個Binder類,在Toast和NMS進行IPC的過程中,當NMS處理Toast的顯示或隱藏請求時會跨進程回調TN中的方法,這個時候由於TN運行在Binder線程池中,所以需要通過Handler將其切換到當前線程中