Android Activity的UI繪製流程之setContentView方法詳解
概述
對於Android開發人員來說,想必對setContentView方法不會陌生,每當我們建立一個Activity時,都會重寫該Activity的onCreate方法,在該方法中我們必須要呼叫setContentView方法來顯示我們指定的佈局或者View。那麼setContentView方法又是如何將我們指定的佈局或者View放入到指定的Activity中顯示出來的呢?
今天這篇部落格主要就是講解Activity的UI繪製流程,而setContentView就是Activity的UI繪製起始過程。
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
這裡先貼一張圖,便於大家更好的理解這個流程
PhoneWindow物件
當我們建立一個Activity時,該類都是預設繼承自Activity方法,所以首先我們需要進入到Activity方法中,然後找到該類的setContentView方法,該方法還有幾個過載的方法,這裡我們主要討論的是定義資源id引數layoutResID的方法,程式碼如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
通過這個方法我們可以看出,在該方法中呼叫了 getWindow().setContentView(layoutResID),那麼這個getWindow又是什麼呢?
mWindow = new PhoneWindow(this, window);
public Window getWindow() {
return mWindow;
}
setContentView方法
通過原始碼原始碼可知,getWindow方法返回的時一個Window類物件,而Window是系統定義一個抽象類,在Activity中我們例項化的是Window的一個實現類PhoneWindow。所以我們需要進入到PhoneWindow類中,檢視setContentView方法具體實現。
但是通過Eclipse或者Android studio是不能直接檢視到PhoneWindow的原始碼的,要想檢視該類的原始碼需要去sdk的原始碼中查詢,具體路徑為: /sdk/sources/android-21/com/android/internal/policy/impl/PhoneWindow.java。
進入該類之後找到setContentView方法,
@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.
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
installDecor方法
通過原始碼可知,當第一次初始化的時候會執行installDecor方法,該方法原始碼如下:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
在installDecor方法中首先會判斷mDecor物件是否為空,如果為空則會呼叫generateDecor方法,generateDecor方法很簡單就是生成一個DecorView物件。
在mDecor物件在執行完成之後又會判斷mContentParent物件是否為空,如果為空則會通過generateLayout方法生成mContentParent,大家注意這個時候mDecor是作為引數傳入到generateLayout方法中,不用質疑在該方法中會對mDecor做一些操作,那麼或許有人會疑問這個DecorView又是什麼呢?
DecorView
DecroView,其實就是PhoneWindow物件的一個內部類,該類繼承自FrameLayout,對於DecorView,這裡先不做過多的詳細介紹,大家只需要知道DecroView其實就是作為Activity的頂級佈局顯示出來的就可以了。
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
....
}
generateLayout方法
generateLayout方法如下:
protected ViewGroup generateLayout(DecorView decor) {
//1,獲取<Application android:theme=""/>, <Activity/>節點指定的themes或者程式碼requestWindowFeature()中指定的Features, 並設定
TypedArray a = getWindowStyle();
//...
//2,獲取視窗Features, 設定相應的修飾佈局檔案,這些xml檔案位於frameworks/base/core/res/res/layout下
int layoutResource;
int features = getLocalFeatures();
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = com.android.internal.R.layout.screen_progress;
//...
mDecor.startChanging();
//3, 將上面選定的佈局檔案inflate為View樹,新增到decorView中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
//將視窗修飾佈局檔案中id="@android:id/content"的View賦值給mContentParent, 後續自定義的view/layout都將是其子View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//...
}
在該方法中,首先會通過getWindowStyle方法獲取window的樣式閒逛屬性並對Window進行一系列的初始化,這裡大家可以看看程式碼,方法開始時這裡通過一系列的判斷呼叫requestFeature方法,對於requestFeature方法想必大家也不陌生,在開發中我們會經常在activity中呼叫該方法來設定FEATURE_NO_TITLE等屬性。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
}
而且該方法的呼叫必須是在setContentView方法之前,或許在一開始大家並不明白為什麼要在setContentView方法之前呼叫requestFeature方法,但是現在應該就明白了。因為在setContentView方法中會間接的初始化Window的屬性,這裡會呼叫requestFeature等方法,如果在開發中我們在setContentView方法之後呼叫requestFeature方法改變一些屬性值,那麼此時window的初始化已經完成,在呼叫requestFeature方法就沒有作用了。
接下來,在generateLayout方法中,mLayoutInflater會根據layoutResource建立一個View物件,這個View物件in會被放入到decor中,也就是之前建立的DecroView中。
而layoutResource的獲取則和之前requestFeature方法初始化有關,這裡我們分析下預設情況下layoutResource的值,也就是 layoutResource = R.layout.screen_simple;,這裡我們可以看看screen_simple的佈局,該佈局的檔案位置在sdk/platforms/android-21/data/res/layout/screen_simple.xml。當然,如果大家有興趣也可以看看其他情況下如actionbar下的佈局。
<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>
在該佈局中的FrameLayout的id為content,想必大家就不陌生了,我們通過setContentView方法指定的佈局最終就是給這個FrameLayout新增一個子佈局。
最後,對DecorView新增完view物件之後,會通過該一下程式碼獲取一個ViewGroup物件,這個物件就是generateLayout方法按最終返回的ViewGroup物件。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
最終我們在回到setContentView方法中,在該方法中我們會填充指定給activity的佈局,並且將mContentParent作為view的跟佈局,而這個mContentParent就是通過generateLayout方法建立的。
@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.
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
總結
對於setContentView方法的分析就到此為止,這裡我們總結一下:
當我們建立一個Activity時,會有一個PhoneWindow的物件被建立,PhoneWindow是抽象類Window的具體實現
在PhoneWindow中有一個內部類DecorView,DecroView是繼承自FrameLayout,該類是所有應用視窗的根View
在DecorView中會新增一個具體的View,該View會根據不同的theme和feature而不同,但是有一個共同點就是在該View中會有一個id為”@android:id/content”的FrameLayout存在,該類將作為activity中顯示指定佈局的父佈局存在,也就是activity顯示的佈局江北新增到這個FrameLayout中
最後貼上一張圖,通過hierarchyviewer工具獲取的activity的view的結構,也可以一一印證上面的結論。
相關推薦
Android Activity的UI繪製流程之setContentView方法詳解
概述 對於Android開發人員來說,想必對setContentView方法不會陌生,每當我們建立一個Activity時,都會重寫該Activity的onCreate方法,在該方法中我們必須要呼叫setContentView方法來顯示我們指定的佈局或者View
Android系統原始碼分析--View繪製流程之-setContentView
上一篇分析了四大元件之ContentProvider,這也是四大元件最後一個。因此,從這篇開始我們分析新的篇章--View繪製流程,View繪製流程在Android開發中佔有非常重要的位置,只要有檢視的顯示,都離不開View的繪製,所以瞭解View繪製原理對於應用開發以及系統的學習至關重要。由於View
Android 多線程之IntentService 完全詳解
required xmlns 抽象 bitmap 圖片 on() 使用 ecif ati 關聯文章: Android 多線程之HandlerThread 完全詳解 Android 多線程之IntentService 完全詳解 android多線程-AsyncTask之
react-redux 之 connect 方法詳解
Redux 是「React 全家桶」中極為重要的一員,它試圖為 React 應用提供「可預測化的狀態管理」機制。Redux 本身足夠簡單,除了 React,它還能夠支援其他介面框架。所以如果要將 Redux 和 React 結合起來使用,就還需要一些額外的工具,其中最重要的莫過於 react-redux 了。
Android 多執行緒之HandlerThread 完全詳解
之前對執行緒也寫過幾篇文章,不過倒是沒有針對android,因為java與android線上程方面大部分還是相同,不過本篇我們要介紹的是android的專屬類HandlerThread,因為HandlerThread在設定思想上還是挺值得我們學習的,那麼我們下面來
jQuery之$.ajax()方法詳解
ajax不管是前端,還是後臺都是要學習的知識點,也是必用知識點。ajax是非同步更新,只需要進行少量的資料互動便可到達頁面的區域性刷下。非常棒。 jquery中的ajax方法引數總是記不住,這裡記錄一下。 1.url: 要求為String型別的引數,(預設為當前
PHP非同步請求之fsockopen()方法詳解
正常情況下,PHP執行的都是同步請求,程式碼自上而下依次執行,但有些場景如傳送郵件、執行耗時任務等操作時就不適用於同步請求,只能使用非同步處理請求。 場景要求: 客戶端呼叫伺服器a.php介面,需要執行一個長達10s-20s不等的耗資源操作,假如客戶端響應請求時間為5秒(請求響應超時時間),5s以上無回覆
android事件匯流排EventBus3.0使用方法詳解
2.EventBus的四種ThreadMode(執行緒模型) EventBus3.0有以下四種ThreadMode: POSTING(預設):如果使用事件處理函式指定了執行緒模型為POSTING,那麼該事件在哪個執行緒釋出出來的,事件處理函式就會在這個執行緒中執行,也就是說釋出事件和接收事件在同一個執行緒。
Mapreduec流程之Shuffle過程詳解
作為整個Mapreduce中最為神祕,複雜的部分,恰恰是平時業務中最經常接觸的地方。僅僅依靠map和reduce階段的業務程式碼編輯,是不能滿足平時的業務需要的。真正的業務處理中,經常會涉及到自定義partition,sort,groupcomparator等情況。而只有瞭
ApplicationContext之getBean方法詳解
我們知道可以通過ApplicationContext的getBean方法來獲取Spring容器中已初始化的bean。getBean一共有以下四種方法原型:l getBean(String name)l getBean(Class<T> type)l getBean
Android中的Handler的post方法詳解
我們都知道Handler中的post方法,並且也是經常使用它 handler.post(new Runnable(){ @Overridepublic void run() {//do something }}); 用它可以更新一個元件的內容,我們也知道Hanlder
Android開發四大元件之Service(詳解篇)
Android開發之四大元件——Service 一、Service 簡介 Service是android系統中的四大元件之一(Activity、Service、BroadcastReceiver、ContentProvider),它跟Activity的級別差不多,區別是Se
Android程式設計學習筆記 之 ListActivity原始碼詳解
前言 最近在研究PreferenceActivity發現是繼承自ListActivity的,開啟看了下ListActivity的原始碼,發現也不長,就詳細閱讀認識一下。 正文 ListActi
關於FileChannel的獲取方式之open方法詳解
mismatch 詳解 -m jdk1.8 eclipse all match try sin FileChannel.open(Path path, OpenOption... options); 例子使用JDK1.8 FileChannel open方法源碼:
一文秒懂!Python字串格式化之format方法詳解
format是字串內嵌的一個方法,用於格式化字串。以大括號`{}`來標明被替換的字串,一定程度上與`%`目的一致。但在某些方面更加的方便 ## 1、基本用法 **1、按照{}的順序依次匹配括號中的值** ```python s = "{} is a {}".format('Tom', 'Boy')
Android View 的繪製流程之 Layout 和 Draw 過程詳解 (二)
View 的繪製系列文章: Android View 繪製流程之 DecorView 與 ViewRootImpl Android View 的繪製流程之 Measure 過程詳解 (一) Android View 的繪製流程之 Layout 和 Draw 過程詳解 (二) 在上一篇 
Android應用層View繪製流程之DecorView與ViewRootImpl
概述 一直對Android中View的整個繪製流程不是很瞭解,View是怎麼新增到Activity當中去的?當View中的內容發生改變的時候是怎樣執行介面的重新整理的?因此,今天準備從原始碼的角度來對View的整個繪製流程來進行分析,原始碼基於API25。由於
android-進階(3)-自定義view(2)-Android中View繪製流程以及相關方法的分析
最近正在學自定義view,這篇文章主要講view的繪製流程和一些相關的方法,淺顯易懂,寫的非常好,忍不住就轉載了。 前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。
[js高手之路]原型對象(prototype)與原型鏈相關屬性與方法詳解
隱式 之前 username tar uname create pro getproto .get 一,instanceof: instanceof檢測左側的__proto__原型鏈上,是否存在右側的prototype原型. 我在之前的兩篇文章 [js高手之路]構造函數的基
Go語言之方法詳解
go語言 方法方法是與對象實例綁定的特殊函數。用於維護和展示對象自身的狀態。對象是內斂的。普通函數則專註與算法流程,通過接受參數來完成特定的邏輯運算,並返回最終結果,方法是有關聯狀態的,函數通常是沒有的。方法和函數定義語法區別在於前者實例接受參數,編譯器以此確定方法所屬的類型。在一些語言中盡管沒有定義,但是函