八、Window

Window表示一個窗口的概念,直接使用的機會不多,在某些特殊的時候,比如需要在桌面上顯示一個類似懸浮窗的東西,那麼這種效果就需要Window來實現。
Window是一個抽象類,在手機上面的具體的實現是PhoneWindow。創建一個Window是很簡單的事,只需要通過WindowManager即可完成。WindowManager是外界訪問Window的入口,Window的具體實現位於WindowManagerService中,WindowManager和WindowManagerService的交互實際上是一個IPC過程。
Android中所有的視圖都是通過Window來呈現的,不管是Activity,Dialog還是Toast,他們的視圖實際都是附加在Window上的。

1.Window和WindowManager

WindowManager.LayoutParams中的flags和type這兩個參數比較重要。
Flags表示Window的幾個屬性。
FLAG_NOT_FOCUSABLE
表示Window不需要獲取焦點,也不需要接收各種輸入事件,次標記同時會啓用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點的Window。

FLAG_NOT_TOUCH_MODAL
在此模式下,系統會將當前Window區域以外的單擊事件傳遞給底層的Window,當前Window區域以內的單擊事件則自己處理。這個標記很重要,一般來說都需要開啓此標記,否則其他Window將無法接收到單擊事件。

FLAG_SHOW_WHEN_LOCKED
開啓此模式,可以讓Window顯示在鎖屏的界面上。

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

Window是分層的,層級大的會覆蓋在層級小的Window上面,在三類Window中,應用類Window的層級範圍是1-99,子Window的層級範圍是1000-1999,系統Window的層級範圍是2000-2999,這些層級範圍對應着WindowManager.LayoutParams的type參數。

如果想要Window位於所有Window的最頂層,採用較大的層級即可。很顯然系統Window的層級是最大的,而且系統層級有很多的值,一般我們選用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,如果採用TYPE_SYSTEM_ERROR,只需要爲type參數指定這個層級即可:

mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;

同時聲明權限:

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

系統類型的Window是需要檢查權限的,不配置權限會報錯。

WindowManager所提供的功能很簡單,常用的只有三個,即添加View,更新View,和刪除View,這三個方法定義在ViewManager中,而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);
}

例子定義一個隨手指移動的Button。

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            int x = (int) event.getX();
            int y = (int) event.getY();
            mLayoutParams.x = rawX;
            mLayoutParams.y = rawY;
            mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
            break;
        case MotionEvent.ACTION_UP:
            break;
        default:
            break;
        }
        return false;
    }
    @Override
    protected void onDestroy() {
        try {
            mWindowManager.removeView(mFloatingButton);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        super.onDestroy();
    }

2.Window的內部機制

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

2.1.Window的添加過程
WindowManager是一個接口,實現類是WindowManagerImpl類。WindowManagerImpl並沒有直接實現Window的三大操作,而是全部交給WindowManagerGlobal來處理。
2.1.1.檢查參數是否合法,如果是子Window那麼還需要調整一些佈局參數;
2.1.2.創建ViewRootImpl並將View添加到列表中;
2.1.3.通過ViewRootImpl來更新界面並完成Window的添加過程;

2.2.Window的刪除過程
Window的刪除過程和添加過程一樣,都是先通過WindowManagerImpl後,再進一步通過WindowManagerGlobal來實現的。
最後會調用dispatchDetachedFromWindow方法,真正刪除view的邏輯在dispatchDetachedFromWindow方法內部實現。

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

2.3.Window的更新過程
需要看WindowManagerGlobal的updateViewLayout方法。除了View本身的重繪以外,ViewRootImpl還會通過WindowSession來更新Window的視圖,這個過程最終是由WindowManagerService的relayoutWindow()來具體實現的,同樣是一個IPC過程。

3.Window的創建過程
View是Android中視圖的呈現方式,但是View不能單獨存在,必須附着在Window這個抽象的概念上面,因此有視圖的地方就有Window。
視圖如:Activity,Dialog,Toast,除此之外,還有一些依託Window而實現的視圖,比如PopupWindow、菜單,他們也是視圖,有視圖的地方就有Window,因此Activity,Dialog,Toast等視圖都對應着一個Window。

3.1.Activity的Window創建過程
首先Activity的啓動過程,Activity的啓動過程很複雜,最終會由ActivityThread中的performLaunchActivity()來完成整個啓動過程,在這個方法內部會通過類加載器創建Activity的實例對象,並調用其attach方法爲其關聯運行過程中所依賴的一系列上下文環境變量。在Activity的attach方法裏,系統會創建Activity所屬的Window對象併爲其設置回調接口,Window對象的創建是PolicyManager的makeNewWindow方法實現的。

Activity的視圖附屬在Window上面,Activity的setContentView方法。
步驟:
3.1.1.如果沒有DecorView,創建DecorView是一個FrameLayout,內部一般包含標題欄和內部欄。ID_ANDROID_CONTENT = com.android.internal.R.id.content
3.1.2.將View添加到DecorView的mContentParent中。
3.1.3.回調Activity的onContentChanged方法通知Activity視圖已經發生變化。

這個時候DecorView還沒有被WindowManager正式添加到Window中,在ActivityThread的handleResumeActivity方法中,首先會調用Act的onResume方法,接着會調用Activity的makeVisible(),正是在makeVisible方法中,DecorView真正地完成了添加和顯示這兩個過程,到這裏Act視圖才能被用戶看到。

3.2.Dialog的Window創建過程
3.2.1.創建Window
Window對象的創建是PolicyManager的makeNewWindow方法實現的,創建後的對象實際上就是PhoneWindow。
3.2.2.初始化DecorView並將Dialog的視圖添加到DecorView中
3.2.3.將DecorView添加到Window中並顯示需要使用Activity來作爲Context來顯示對話框,系統對話框不需要這樣,比如可以設置type,dialog.getWindow.setType(LayoutParams.TYPE_SYSTEM_ERROE);
需要SYSTEM_ALERT_WINDOW權限。

3.3.Toast的Window創建過程
Toast也是基於Window來實現的,但是由於Toast具有定時取消這一功能,所以系統採用了Handler。在Toast的內部其實有兩兩類IPC過程,第一類是Toast訪問NotificationManagerService,第二類NMS回調Toast裏的TN接口。

Toast屬於系統Window,它內部的視圖由兩種方式指定,一種是系統默認樣式,另一種是通過setView方法來指定一個自定義的View,不管如何,它們都對應Toast的一個View類型的內部成員mNextView。

Toast的顯示和隱藏過程實際上是通過Toast中的TN這個類來實現的,它有兩個方法show和hide,分別對應Toast的顯示和隱藏。由於這兩個方法是被NMS以跨進程的方式調用的,因此它們運行在Binder線程池中,爲了將執行環境切換到Toast請求所在的線程,在它們的內部使用了Handler。

TN中的handleShow中會將Toast的視圖添加到Window中,TN中的handleHide中會將Toast的視圖從Window中移除。

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