setContentView到底做了什麼

對Activity的setContentView的使用,大家比較熟悉了,當然,對setContentView的原理估計也比較熟悉,網上有不少的文章,不過,還是寫一篇這方面的東西,記錄下,畢竟

很多東西,看別人的是一回事兒,自己寫又是一回事兒,當作是對知識的溫故,再學習吧!

Activity.java

首先,在自己的activity中,調用setContentView(int resID)方法,如下:

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
    }

可以看出,該方法先是獲取Window的對象,然後調用該對象的setContentView方法

繼續看,源碼:

    private Window mWindow;

   public Window getWindow() {
        return mWindow;
    }

    final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,...省略) {
        mWindow = new PhoneWindow(this);
        //省略部分代碼
    }

發現getWindow()方法,是mWindow,屬於成員變量,在attach方法調用後mWindow初始化爲PhoneWindow,是不是着急想知道PhoneWindow是何東東?

別急,在這有點疑問?attach方法在什麼時候調用呢?告訴你,是在ActivityThread類中,有個performLaunchActivity方法,該方法調用activity.attach,然後mWindow初始化

當然,關於performLaunchActivity執行,暫時只需要知道,當我們調用startActivity的時候,系統會去加載一系列的類,調用一系列方法,其中就有performLaunchActivity,來

完成activity的啓動工作

ok,知道了mWindow的初始化,讓我們回到setContentView,來聊聊PhoneWindow這個類,PhoneWindow繼承自Window,而Window是個抽象類

PhoneWindow.java

    public class PhoneWindow extends Window  

PhoneWindow對setContentView的重寫,如下: 

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        }
        //...
       mLayoutInflater.inflate(layoutResID, mContentParent);
        //...
   }
這裏,知道setContentView執行了兩個方法,

       步驟1:installDecor(),

       步驟2:將我們自己的xml佈局資源ID,轉換爲view,並填充到mContentParent上,問題來了,mContentParent是什麼呢?留個問題在這

先看步驟1,源碼如下:

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            //...
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            //...
        }
確認DecorView是FrameLayout的子類
    private final class DecorView extends FrameLayout

到這裏,知道步驟1是執行了兩步,瞭解到以下內容:

        mDecor是個類變量,是DecorView,繼承Framlayout,也就是一個ViewGroup,mDecor是generateDecor創建的

        mContentParent是類變量,是一個ViewGroup,mContentParent是generateLayout進行創建的,mContentParent的創建依賴mDecor,

接下來,分別看generateDecor與generateLayout

generateDecor()方法:

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

該方法創建了DecorView實例,通過查看DecorView構造方法,發現只是初始化了一些類變量,沒有其他操作

generateLayout()方法(關鍵之處):

PS:這裏,由於源碼比較冗雜,只是保留關鍵部分,簡化代碼如下

        
    protected ViewGroup generateLayout(DecorView decor){
        int layoutResource;
        //...
        layoutResource = R.layout.screen_simple;

        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        //...
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        //...
        return contentParent;

    }
layoutResource的值是經過一系列判斷後才被賦值的,這裏,我選擇的其中一個固定值,佈局填充器將系統資源文件Id爲R.layout.screen_simple佈局轉換爲View對象in,

in作爲DecorView的子view進行添加,調用findViewById,這裏,很奇怪,爲何能直接調用findViewById,原來是Window中定義了該方法,前面說過,PhoneWindow繼承

Window,所以可直接調用父類方法。

Window

    public View findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

    public abstract View getDecorView();

那麼,getDecorView()是怎麼執行的呢?PhoneWindow對該抽象方法進行具體實現,返回上面generateDecor()方法的mDecor

PhoneWindow

    public final View getDecorView() {
        if (mDecor == null) {
            installDecor();
        }
        return mDecor;
    }

到這裏,大致清楚了,這句代碼

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

饒來繞去,就是在mDecor中查找ID_ANDROID_CONTENT的對應的View,然後是賦值給contentParent,在generateLayout方法中返回contentParent,最終賦值給

mContentParent,換句話說,mContentParentmDecor的子view,而我們自己佈局對應的view,是mContentParent的子view

步驟2

    mLayoutInflater.inflate(layoutResID, mContentParent);
    //等價於
    View ourContentView = mLayoutInflater.inflate(layoutResID, null);
    mContentParent.addView(ourContentView );

R.layout.screen_simplexml源碼:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:fitsSystemWindows="true"
       android:orientation="vertical">
      <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
      <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
    </LinearLayout>

結論:DecorViewWindow的最頂級的View,其下有兩個子View,一個標題欄bar,一個是容器content

   自己定義的佈局對應view,是被addView在容器content

最後,附上一張圖,直觀感受下

發佈了41 篇原創文章 · 獲贊 18 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章