Android setContentView()源碼解析

來源:互聯網
上載者:User

標籤:

前言

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

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

建立測試工程TestHierarchy

建立工程後,activity_main.xml中預設只有一個RelativeLayout,其中包含一個TextView。

設定android:id="@+id/myRelativeLayout"android:id="@+id/myTextView"

設定MainActivity繼承自Activity。

保持預設MainActivity extends AppCompatActivity

保持預設<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">

至此初始工作完畢。

使用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的Fragment是contentParent,最外層的LinearLayout為mContentView。id為action_mode_bar_stub的android:visibility="gone"。最後放出一張自己標註的圖~


更多Framework源碼解析,請移步 Framework源碼解析系列[目錄]

Android setContentView()源碼解析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.