Android setContentView()源碼解析

前言java

在Activity中通常第一句就是調用setContentView(R.layout.XXX),但這其中系統作了那些工做?android

咱們知道,在ClassLoader裝載了MainActivity以後,首先建立了Application,以後依次調用Application對象的onAttach和onCreate()方法。而後順序調用第一個Activity的onAttach和onCreate()方法。大概有個印象便可,後文會涉及到。具體參考:Launcher啓動應用程序流程源碼解析。app

新建測試工程TestHierarchyide

新建工程後,activity_main.xml中默認只有一個RelativeLayout,其中包含一個TextView。工具

設置Android:id="@+id/myRelativeLayout",android:id="@+id/myTextView"。佈局

設置MainActivity繼承自Activity。測試

保持默認MainActivity extends AppCompatActivitythis

保持默認<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">.net

至此初始工做完畢。xml

使用hierarchy查看佈局結構

hierarchy是隨着SDK發佈的一款可視化佈局分析工具。這裏只須要基礎的查看佈局層次。因爲Hierarchy Viewer只能鏈接Android開發版手機或是模擬器,因此咱們先在虛擬機上運行程序,而後進入..\sdk\tools,找到hierarchyviewer.bat,雙擊。接着選中咱們的程序,以後點擊Load View Hierarchy,以後會獲得一個黑不溜秋的視圖。而這裏,就是重點要看的地方。可是爲了方便理解,省去一部分沒必要要的Tiltle等元素,這裏以繼承Activity爲例進行解析。能夠先點擊各個節點試試每一個View對應的位置。

MainActivity繼承Activity

這裏注意下右上角的兩個節點,這明顯就是activity_main.xml。不信看id!

源碼解析

源碼位置:frameworks/base/core/Java/android/app/Activity.java

Activity#setContentView()

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

因爲繼承的是Activity,首先咱們就省略了initWindowDecorActionBar()這一步。Activity大法好~。接下來要關注的就一行代碼。首先看下這個getWindow()返回的是個什麼鬼。

    public Window getWindow() {
        return mWindow;
    }

前言中說Activity的建立的時候第一個執行的方法就是attach()。這裏的mWindow就是在attach()方法中被實例化的。

    final void attach(...){
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
    }

mWindow是個Window對象,可是PhoneWindow繼承於Window。經過PhoneWindow獲取到mWindow以後設置了一個回調。Activity實現了Window.Callback接口,並且Activity中持有一個Window的引用,這就意味着在調用Callback接口方法的時候,Activity能夠獲得相應的回調。而且Activity能夠經過Window屬性去操做View。跟進getWindow().setContentView(layoutResID)。

源碼位置:frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

PhoneWindow#setContentView()

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        // 返回false,執行else分支
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

先大概分析這段代碼流程,首先判斷mContentParent是否是爲空,第一次進來什麼也沒幹吶,鐵定爲null。FEATURE_CONTENT_TRANSITIONS屬性用於設置Activity的切換效果,默認false。上面首先調用了installDecor(),從上下文名稱來看,這個方法應該和mContentParent變量有關係。接着調用mLayoutInflater.inflate(layoutResID, mContentParent)將咱們設置的R.layout.XXX填充到mContentParent。結合前面hierarchyviewer圖來看,mContentParent就是包含activity_main.xml的FrameLayout的一個實例。最後回調Callback#onContentChanged(),這裏的cb其實就是Activity對象。這個方法在Activity中的實現爲空方法,因此咱們能夠在本身的Activity中複寫這個方法,實現本身的邏輯。在Activity的佈局文件發生改動,即調用setContentView()或者addContentView()以後會調用onContentChanged()方法。跟進installDecor(),下面重點解析mDecor和mContentParent。

PhoneWindow#installDecor()

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            ...
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
        }
        ...
}

首先調用generateDecor()方法獲取mDecor實例,接着依據mDecor實例獲取到mContentParent。跟進。

PhoneWindow#generateDecor()

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

    private final class DecorView extends FrameLayout

在PhoneWindow#generateDecor()中直接new了一個DecorView 對象,能夠看到:DecorView也只是個繼承FrameLayout的ViewGroup。下面跟進generateLayout()。

PhoneWindow#generateDecor()

    protected ViewGroup generateLayout(DecorView decor) {
        // 獲取自定義屬性window
        TypedArray a = getWindowStyle();
        ...
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        }
        ...
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        }
        ....  
        else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        mDecor.startChanging();

        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        mDecor.finishChanging();
        return contentParent;
    }

前面省略的一大段的做用是獲取自定義屬性window以後所作的各類初始化工做,這裏以requestFeature(FEATURE_NO_TITLE)爲例。由於在這以後才執行View in = mLayoutInflater.inflate(layoutResource, null),將系統依據style採用的佈局文件轉換爲View in,這裏繼承的是Activity,style=Theme.AppCompat.Light.DarkActionBar,因此加載的佈局爲screen_title.xml。以後將in加入到mDecor中,接着將in賦值給mContentRoot。這裏的public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content。佈局文件screen_title.xml以下所示:

<?xml version="1.0" encoding="utf-8"?>
<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>

id爲content的Framelayout是contentParent,最外層的LinearLayout爲mContentView。id爲action_mode_bar_stub的android:visibility="gone"。最後放出一張本身標註的圖~

總結圖

相關文章
相關標籤/搜索