Android淺談Window,WindowManager,WindowMangerService

有時候咱們須要在桌面上顯示一個相似的懸浮窗的東西,這種效果就須要用Window來實現,Window是一個抽象類,表示一個窗口,它的具體實現類是PhoneWindow,實現位於WindowManagerService中。java

一,Window

window 是一個抽象類,具體實現是 PhoneWindow
window 也是一個抽象的概念,每一個 window 內對應這一個 DecorView 和一個 ViewRootImpl, window 和 DecorView 經過 ViewRootImpl聯繫android

Window有三種類型,分別是 應用Window,子Window,和系統Windowweb

  1. 應用Window: 應用Window對應一個Activity
  2. 子Window : 子Window不能單獨存在,須要依附在特定的父Window中,好比常見的一些Dialog就是一個子Window。
  3. 系統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的層級是最大的,當採用系統層級時,須要聲明權限。app

二,WindowManager

2.1 簡介

WindowManager是外界訪問Window的入口。
咱們對Window的操做是經過WindowManger來完成的,WindowManager是一個接口,它繼承自只有三個方法的ViewManager接口,
WindowManager的實現類是WindowManagerImpl框架

public interface ViewManager{
    // 添加View
    public void addView(View view, ViewGroup.LayoutParams params);
    // 更新 View
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    // 刪除 View
    public void removeView(View view);
}
複製代碼

這三個方法其實就是 WindowManager 對外提供的主要功能,即添加 View、更新 View 和刪除 View異步

WindowManager添加Window的例子:ide

  public class MainActivity extends AppCompatActivity{

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

        Button floatingBtn = new Button(this);
        floatingBtn.setText("button");

        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
            WdinowManager.LayoutParams.WRAP_CONTENT,
            WdinowManager.LayoutParams.WRAP_CONTENT,
            0,0,
            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 windowManger = getWindowManager();
        // 添加視圖
        windowManger.addView(floatingBtn,layoutParams);

     }
  }
複製代碼

代碼中並無調用Activity的setContentView 的方法,而是直接經過WindowManager添加Window,其中設置爲系統Window,因此應添加權限oop

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
複製代碼

結果以下圖:佈局

button添加到Window
button添加到Window

第二個界面是鎖屏界面,因爲按鈕是處於較大層級的系統 Window 中的,因此能夠看到 button。

2.2 WindowManager內部機制

WindowManager的繼承關係:

WindowManager的繼承關係UML
WindowManager的繼承關係UML

在實際使用沒法直接訪問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);
        }
複製代碼

能夠看到,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. 建立 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>();
複製代碼

其中 :
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. 經過 ViewRootImpl 來更新界面並完成 Window 的添加過程
    在學習 View 的工做原理時,咱們知道 View 的繪製過程是由 ViewRootImpl 來完成的,這裏固然也不例外,具體是經過 ViewRootImpl 的 setView 方法來實現的。在 setView 內部會經過 requestLayout 來完成異步刷新請求,以下:
public void requestLayout(){
   if(!mHandingLayoutInLayoutRequest){
       checkThread();
       mLayoutRequested = true;
       scheduleTraversals();
   }
}
複製代碼

能夠看到 scheduleTraversals 方法是 View 繪製的入口,繼續查看它的實現:

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), 
          mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);
複製代碼

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);
}
複製代碼

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

WindowManager的addView的過程
WindowManager的addView的過程

理解了 Window 的添加過程,Window 的刪除過程和更新過程都是相似的,也就容易理解了,它們最終都會經過一個 IPC 過程將操做移交給 WindowManagerService 這個位於 Framework 層的窗口管理服務來處理。

三. WindowManagerService

WindowManagerService就是位於FrameWork層的窗口管理服務,它的職責就是管理全部窗口。窗口的本質是什麼呢?其實就是一塊顯示區域,在Android中就是繪製的畫布:Surface。當一塊Surface顯示屏幕上時,就是用戶所看到的窗口了。WindowManagerServce添加一個窗口的過程,其實就是WindowManagerService爲其分配一塊Surface的過程,一塊塊的Surface在WindowManagerService管理下有序的排列在屏幕上,Android才呈現出多姿多彩的界面。因而根據對Surface的操做類型能夠將Android的顯示系統分爲三個層次:

WindowManagerService
WindowManagerService

通常開發中,咱們操做的是UI框架層,對Window的操做經過WindowManager便可完成,而WindowManagerService做爲系統及服務運行在一個單獨的進程,全部WindwowManager和WindowManagerService的交互是一個IPC的過程

四,Window的建立過程

View是Android的視圖呈現方式,可是View不能單獨存在,它必須附着在Window這個抽象的概念之上,所以有視圖的地方就有Window。

哪些地方有Window呢?Android 能夠提供視圖的地方有 Activity、Dialog、Toast,除此以外,還有一些依託 Window 而實現的視圖,好比 PopUpWindow(自定義彈出窗口)、菜單,它們也是視圖,有視圖的地方就有 Window。
所以 Activity、Dialog、Toast 等視圖都對應着一個 Window。這也是面試中常問到的一個知識點:一個應用中有多少個 Window?下面分別分析 Activity、Dialog以及 Toast 的 Window 建立過程。

1. Activity 的 Window 建立過程
Window本質就是一塊顯示區域,因此關於Activity的Window建立應該發生在Activity的啓動過程。Activity的啓動過程很複雜,最終會由ActivityThread中的performanLaunchActivity()來完成整個啓動過程,在這個方法內部會經過類加載器建立Activity的實例對象,並調用其attach方法爲其關聯運行過程當中所依賴的一系列上下文環境變量。

Activity的Window實例是怎麼建立的?

Activity的Window建立就發生attach方法裏,系統會建立Activity所屬的Window對象併爲其設置回調接口,代碼以下:
Activity的attach方法,內部方法,由ActivityThread的調用:

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback)
 
{
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        // 實例化Window對象,是PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        // Activity實現了Window.Callback,如下是設置回調

        // 設置Window控制器回調
        mWindow.setWindowControllerCallback(this);
        // 設置Window回調
        mWindow.setCallback(this);
        // 設置Window消失的回調
        mWindow.setOnWindowDismissedCallback(this);

        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        // UI線程
        mUiThread = Thread.currentThread();
        // 主線程
        mMainThread = aThread;
        mInstrumentation = instr;
        // 所依附的Window的token
        mToken = token;
        mIdent = ident;
        // 所在的application信息
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        // 此activity的信息,和xml中的一致
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, thisthis,
                        Looper.myLooper());
            }
        }
        // 此activity的Window設置 WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        // 賦值WindowManager
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
    }
複製代碼

此方法有兩個做用:
1,實例化Window對象,是PhoneWindow的實例
2,爲此Activity的Window對象設置Callback,是Window.Callback早已被Activity實現
3,賦值其餘的線程,Activity信息,Window設置WindowManager等

Window.Callback早已被Activity實現:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.CallbackKeyEvent.Callback,
        OnCreateContextMenuListenerComponentCallbacks2,
        Window.OnWindowDismissedCallbackWindowControllerCallback,
        AutofillManager.AutofillClient 
{
複製代碼

Callback接口中的方法有不少,有咱們熟悉的onAttachedToWindow,onDetachedFromWindow,dispatchTouchEvent等等

Callback的接口的方法
Callback的接口的方法

可見,Activity immplements Window.Callback 的接口的方法後,Window設置回調mWindow.setCallback(this),全部此Activity的Window的的對外回調狀態都是在Activity中,若是想知道Window的狀態,重寫Activity內實現的Window.Callback方法就好。

下面分析Activity的視圖是怎麼附屬到Window上的?

咱們知道Activity的視圖由setContentView提供,因此從Activity#setContentView入手:

/**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */

    public void setContentView(@LayoutRes int layoutResID) {
        // 可見實際是把 佈局文件 設置到 Window上
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
複製代碼

而Window的具體實現是PhoneWindow,只須要看PhoneWindow的相關邏輯:

    ...

    // This is the top-level view of the window, containing the window decor.
    // 這是Window的頂層View,包含Content佈局
    private DecorView mDecor;

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    // 這是View是 Window的Content佈局,要麼是Decor自身,要麼是Decor的Content的地方,其實例來自上面 mDecor
    ViewGroup mContentParent;

    ...

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.

        // 1,若是內容佈局是null,就初始化DecorView,併產生Content佈局
        if (mContentParent == null) {
            installDecor();

        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 2,把咱們的xml佈局,添加到 DecorView裏的 Content佈局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            // 3, 回到Activity的 onContentChanged方法,通知咱們的xml佈局已經附屬在 DecorView上了
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

    // 就初始化DecorView,併產生Content佈局
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }

        // 若是Content佈局是null,就從DecorView 中初始化出來
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            }

        .......其餘代碼
        }
複製代碼

因此,從上面的PhoneWindow的源碼能夠看出

  1. PhoneWindow裏面含有DecorView和Content佈局實例
  2. 添加xml佈局文件時setContentView
  3. 若是沒有 DecorView 就建立一個
    DecorView 是 Activity 中的頂級 View,是一個 FrameLayout,通常來講它的內部包含標題欄和內容欄,可是這個會隨着主題的變化而改變,無論怎麼樣,內容欄是必定存在的,而且有固定的 id:」android.R.id.content」,在 PhoneWindow 中,經過 generateDecor 方法建立 DecorView,經過 generateLayout 初始化主題有關佈局。
  4. 將 View 添加到 DecorView 的 mContentParent 中
    這一步較爲簡單,直接將 Activity 的視圖添加到 DecorView 的 mContentParent 中便可,由此能夠理解 Activity 的 setContentView 這個方法的來歷了,爲何不叫 setView 呢?由於 Activity 的佈局文件只是被添加到 DecorView 的 mContentParent 中,所以叫 setContentView 更加具體準確。
  5. 回調 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);
}
複製代碼

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);
}
複製代碼

(3)、將 DecorView 添加到 Window 中並顯示
在 Dialog 的 show 方法中,會經過 WindowManager 將 DecorView 添加到 Window 中,以下:

mWindowManager.addView(mDecor, 1);
mShowing = true;
複製代碼

從上面三個步驟能夠發現,Dialog 的 Window 建立過程和 Activity 建立過程很相似,當 Dialog 關閉時,它會經過 WindowManager 來移除 DecorView。普通的 Dialog 必須採用 Activity 的 Context,若是採用 Application 的 Context 就會報錯。這是由於沒有應用 token 致使的,而應用 token 通常只有 Activity 擁有,另外,系統 Window 比較特殊,能夠不須要 token.

相關文章
相關標籤/搜索