深刻剖析Window

本文是Android視圖層源碼分析系列第一篇文章。主要來理清Window的地位以及做用。java

Android中全部的視圖(View)都是經過Window來呈現的,無論是ActivityDialog仍是Toast,它們的視圖實際上都是附加在Window上的,所以Window實際是View的直接管理者。本文就從源碼來分析一下Window,理清Window是如何組織視圖(View)以及ActivityPhoneWindow的工做原理。本文不會去討論Window的詳細使用。android

分析以前,咱們先找一個切入點,如下面這段代碼爲例:git

WindowTestActivity.javagithub

// example 1
val simpleTv = getSimpleTextView()
windowManager.addView(simpleTv, getSimpleWindowLayoutParams()) 

//example 2
window.addContentView(getSimpleTextView(), ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
複製代碼

即咱們直接經過windowManager.addViewwindow.addContentView()來添加了一個View。這兩個方法都是Activity直接提供的方法,也是咱們惟一與Window交互的幾個方法之一,那:bash

  1. 這兩個方法有什麼關係與不一樣呢?
  2. View究竟添加到了哪裏呢?
  3. windowwindowManager有什麼關係呢?

下面咱們將從源碼一點一點弄清這些問題。先來看一下windowManager.addView(contentView, layoutParams),爲了下面方便敘述,咱們把被addview叫作contentView微信

經過WindowManager添加一個View

WindowManager實例的建立

WindowManager是一個接口,在看windowManager.addView()以前咱們先來看一下Activity的WindowManager的實例是誰。app

追蹤Activity的源碼發現WindowManager實際上是經過Window來獲取的(它實際上是Window的成員變量)源碼分析

mWindowManager = mWindow.getWindowManager();
複製代碼

WindowWindowManager是在什麼地方賦值的呢?實際上是在Activity attch時:佈局

//Activity.java
final void attach(...){
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    ...
    mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken,..);
}
複製代碼

mWindow.setWindowManager()內部實際上是構造了一個WindowManagerImpl:ui

public void setWindowManager(WindowManager wm, IBinder appToken...) {
    ...
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
複製代碼

ActivityPhoneWindowWindowManager實例是WindowManagerImpl

而且在Activity.attach方法中也能夠看出ActivityWindow的實例時PhoneWindow(PhoneWindow實際上是Window的惟一實現類,是針對於app客戶端(相對於Android系統)的一個Window實體)。

WindowManagerImpl其實只是一個簡單的裝飾類,全部操做直接轉發到了WindowManagerGlobal, 所以windowManager.addView()源碼的追蹤能夠直接看WindowManagerGlobal.addView():

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View panelParentView = null;
    ...

    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    root.setView(view, wparams, panelParentView);
}
複製代碼

parentWindow這個參數其實就是Activitywindow(PhoneWindow)。而WindowManagerGlobal.addView()作的主要事情是:

  1. 構造了一個(root)ViewRootImpl
  2. 分別把contentView相關對象放入到mViews/mRoots/mParams集合中。(若是contentView被移除,那麼這3個集合相關對象也會被移除)
  3. root.setView(contentView..)會經過IPC調用到WindowManagerService來在window中顯示contentView

因此windowManager.addView()作的事情是:

contentView建立一個ViewRootImpl對象,並把contentView相關對象放入到mViews/mRoots/mParams集合中維護起來,而後調用ViewRootImpl.setView(..)方法來顯示contentView

因此到這裏能夠用下面這張圖總結一下Activity/Window/WindowManager之間的關係:

通過上面的分析咱們還知道 : 經過windowManager.addView(contentView)來顯示視圖實際上是和ActivityWindow有着密切的聯繫的(顯示一個視圖必需要有Window)。那Activity的視圖是怎麼顯示的呢?

咱們繼續看一下(其實Activity的視圖也是經過windowManager.addView(contentView)的方式來顯示的):

Activity的視圖的顯示

追蹤Activity.setContentView(..)源碼能夠看到:

getWindow().setContentView(contentView);
複製代碼

即咱們的contentView實際上是設置給了Window(PhoneWindow):

PhoneWindow的視圖層級

PhoneWindow.java

public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    ...
    mContentParent.addView(view, params);
}
複製代碼

即咱們Activity的根佈局View實際上是添加到了PhoneWindow的mContentParent成員變量中中。那mContentParent是什麼呢?看一下PhoneWindow.installDecor(),這個方法也很長,所以只截取最重要的部分看一下:

private void installDecor() {
    mDecor = generateDecor(-1);  // decor 的實例時DecorView,它繼承自FrameLayout
    ...
    mDecor.setWindow(this);  //DecorView 綁定一個window
    ...
    mContentParent = generateLayout(mDecor); // mContentParent 會被add到 decor view中
    ...  
}
複製代碼

根據上面的註釋,咱們能夠先這樣理解PhoneWindow/DecorView/mContentParent的關係:

PhoneWindow裏存在一個DecorView(mDecor)成員變量,能夠把它理解爲一個FrameLayout,它包含一個mContentParent的子View, mContentParentActivity的根佈局contentView的父View

mContentParent是一個什麼樣的佈局/View呢?繼續看一下generateLayout(mDecor):

protected ViewGroup generateLayout(DecorView decor) {

     int layoutResource; //mContentParent的佈局文件

    int features = getLocalFeatures();

    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        ....各類 if else
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
    }

    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //會把這個佈局文件inflate出的view,添加到DecorView中

    //經過 findViewById 來獲取 ContentParent。 這個id其實就來自 layoutResource 所指向的佈局文件
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

}
複製代碼

上面我作了一些註釋,能夠理解爲mContentParent就是DecorView的子View。layoutResource根據當前ActivityTheme的設置,會對應到許多不一樣的佈局文件,R.layout.screen_toolbar是給Activity設置默認 Theme是所對應的佈局文件:

<com.android.internal.widget.ActionBarOverlayLayout
    android:id="@+id/decor_content_parent"
    ...>
    <FrameLayout android:id="@android:id/content"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent" />
    <com.android.internal.widget.ActionBarContainer
        android:id="@+id/action_bar_container"
        ...
        android:gravity="top">
        <Toolbar
            android:id="@+id/action_bar"
            ... />
        <com.android.internal.widget.ActionBarContextView
            android:id="@+id/action_context_bar"
            ..../>
    </com.android.internal.widget.ActionBarContainer>
</com.android.internal.widget.ActionBarOverlayLayout>
複製代碼

看一個具體的Android Layout Inspectot分析:

ContentFrameLayoutsupport v7的類,能夠把它理解爲FrameLayout

PhoneWindow的視圖層級能夠用下圖表示

通過上面的分析: PhoneWindow的視圖層級其實就是DecorView的視圖層級。DecorView就是Activity視圖的根View

因此Activity的視圖顯示的過程其實就是DecorView的視圖顯示的過程。那DecorView如何顯示呢?

它的顯示原理也是使用windowManager.addView() :

DecorView的顯示

Activity Resume時,DecorView會添加到WindowManager中:

ActivityThread.java

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ...
    final Activity a = r.activity;
    ...
    wm.addView(decor, l);
}
複製代碼

即在Activity ResumeDecorView添加到WindowManager中,進而經過WindowManagerService來顯示成功。因此Activity.onResume()用戶才能夠看到Activity的視圖。

總結

總結一下到目前爲止所分析的點:

  1. 視圖(View)的顯示離不開Window
  2. WindowManager屬於Window,負責管理WindowView的顯示。在Window中顯示View咱們應使用它的接口
  3. 一個Window能夠有多個子View,每一個子View都對應一個ViewRootImpl
  4. ViewRootImpl會經過IPC來與WindowManagerService交互,來實現View的顯示

它們之間的關係以下圖:

下一篇文章將繼續分析ViewRootImpl.setView(..)所引發的WindowManagerService的操做,即Window是怎麼在屏幕上展現內容的。

歡迎關注個人Android進階計劃看更多幹貨

歡迎關注個人微信公衆號:susion隨心

參考文章:

  • 《Android開發藝術探索》
相關文章
相關標籤/搜索