一行簡單的
setContentView()
背后也會有大量的底層工作炸客。往常總是手快的敲下這一行代碼打毛,甚至使用 AS 自動創(chuàng)建 Activity 都不用自己敲這一行代碼速蕊,但是你有沒有想過這一行簡單代碼背后的機制呢?這次就一起來看看乖仇。
Hierarchy View
在對這行代碼背后的機制進行分析之前憾儒,要先學(xué)會如何去看一個具體的 Activity 是由哪些部分構(gòu)成的,這對我們接下來的理解有很大的幫助乃沙。Hierarchy View 可以用圖形化的視圖來展示界面的組成結(jié)構(gòu)起趾。打開 AS 的 DDMS 視圖,也就是 Android Device Monitor 這個工具警儒。點擊頂部工具欄 DDMS 按鈕左邊的 Open Perspective
按鈕训裆,選擇 Hierarchy View
,就可以進入了。點擊左邊的小手機圖標(biāo)就能選擇具體的 Activity 來查看組成結(jié)構(gòu)边琉。不過這個工具只能用來看虛擬機上的 Activity属百,實體機目前還不支持。
上面就是我的某個 App 的一個 Activity 界面的組成結(jié)構(gòu)变姨。其最左邊诸老,也就是最頂層的視圖,是一個 PhoneWindow钳恕,同時也是一個 DecorView别伏。記住這兩個詞,后面會用到忧额。
Activity#setContentView()
我們知道厘肮,Activity 是所有類型的 Activity 的父類,那么自然 setContentView
這個方法最開始也是在 Activity 這個類中定義的睦番。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
從代碼里看得出來类茂,(系統(tǒng))拿到了一個 window 并把布局 id 設(shè)置到了這個 window 里,然后初始化了 window 的 Decor 和 ActionBar托嚣,就這么兩件事情巩检。首先看看拿到的 window 是什么:
public Window getWindow() {
return mWindow;
}
mWindow 是一個抽象類 Window, 而 mWindow 這個變量在整個 Activity 中只有一處被初始化:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window);
...
mWindow.setCallback(this);
...
}
PhoneWindow示启?好像有點眼熟兢哭,在第一步 Activity 的組織結(jié)構(gòu)里,最左邊的不就是個 PhoneWindow 么夫嗓?那么這個 PhoneWindow 應(yīng)該就是一個界面最頂層的 View 了迟螺,不過在看 PhoneWindow 的源碼之前,先看看它的抽象類 Window 的說明:
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
...
}
Window 是一個頂層窗口(界面)的外觀和行為的代理舍咖,是個抽象類矩父。這個類的實例應(yīng)該當(dāng)做頂級 View 添加到 window manager 中。這個類提供基本的 UI排霉,例如背景窍株、標(biāo)題區(qū)域、默認的按鍵處理程序等攻柠。同時 PhoneWindow 這個類也是 Window 的唯一實現(xiàn)類球订。當(dāng)你要顯示一個視窗的時候你就必須實例化這個類(PhoneWindow)。也就是說辙诞,setContentView()
最終會調(diào)用 PhoneWindow 里的 setContentView()
方法辙售。
PhoneWindow#setContentView()
下面是 PhoneWindow 中的代碼:
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);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
這個方法中的第二行 insallDecor
這個方法確保了 mDecor 和 mContentParent 這兩個變量都已經(jīng)被實例化,將 mDecor 和 window 進行綁定飞涂,同時初始化視窗的標(biāo)題欄旦部,并從實例化的 window 中獲取一些視窗標(biāo)記(flag)祈搜,還記得我們?yōu)榱穗[藏 Activity 的標(biāo)題欄而調(diào)用 requestWindowFeature()
這個方法么,就是與這里是相同類型的標(biāo)記士八,因為獲取這些標(biāo)記的過程是在 setContentView
里進行的容燕。如果我們想讓自定義的標(biāo)記生效,就得在 setContentView()
這個方法之前調(diào)用 requestWindowFeature()
婚度。
那么 mContentParent 又是什么呢:
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
這個 view 是用來放置視窗內(nèi)容的(就是后期由我們自行添加的區(qū)域)蘸秘,要是隱藏了系統(tǒng)狀態(tài)欄,它的大小就會跟它的上一層 mDecor 相同蝗茁。也就是說醋虏,系統(tǒng)的狀態(tài)欄是處于 mDecor 內(nèi)部、mContentParent 外部的哮翘。
在實例化 mDecor 和 mContentParent 之后颈嚼,就開始解析傳進來的布局文件,將這個布局設(shè)置到 mContentParent 里面饭寺,最后通知 Activity 布局已經(jīng)發(fā)生變化:首先 Activity 是實際的視窗的控制者阻课,不通知它還能通知誰呢?其次呢艰匙,確實就是這么回事限煞,還記得前面 Acticity 中的 attach 方法么,里面有這么一句:mWindow.setCallback(this);
员凝,而 mWindow 就是我們現(xiàn)在在討論的 PhoneWindow 了署驻,所以鐵證如山啊。
Activity#insallDecor()
而關(guān)于 insallDecor
這個方法的具體情況绊序,內(nèi)容比較多硕舆,就不貼代碼了,只要知道:
- mDecor 是通過
generateDecor()
這個方法生成的骤公,這個方法獲取當(dāng)前所能得到最高級別的 Context 來生成 DecorView 的實例,就是有 ApplicationContext 就盡量用 ApplicationContext扬跋; - mContentParent 是通過
generateLayout()
這個方法生成的阶捆,在這個方法里,會根據(jù)預(yù)設(shè)的 Window Style 來對布局文件多進行定制钦听,也就是多次調(diào)用requestWindowFeature()
這個方法洒试,根據(jù) Feature 的不同,還會選擇不同的布局作為 DecorView 的初始布局朴上。
Activity#initWindowDecorActionBar()
到這里 PhoneWindow 的 setContentView
就解讀完了垒棋,在開始的 Activity 里的 setContentView
方法中還有一句代碼:initWindowDecorActionBar();
我們也來看看這個方法:
private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
這個方法是對初始化布局的進一步補充,在 PhoneWindow 里知識加載出一些默認的布局比如標(biāo)題欄痪宰,那么這里就是對默認的標(biāo)題欄進行一些初始的設(shè)置叼架,比如在 manifest 文件中給一個 Activity 設(shè)置 label 屬性畔裕,那么打開 Activity 就會直接顯示 label 的內(nèi)容,這個 label 就是在這里被設(shè)置的乖订。
總結(jié)
結(jié)合前面這些加載過程的詳情和最開始的組織結(jié)構(gòu)扮饶,能夠得到下面這個更直觀的視窗組織結(jié)構(gòu)示意圖:
- 系統(tǒng) Launcher 界面:每一個 App 的圖標(biāo)都是在系統(tǒng) Launcher 里面的一個按鈕,通過按鈕打開我們自己的 App乍构,所以 Activity 外面就是 Launcher 的界面的了甜无;
- 應(yīng)用窗口(mWindow):這個就是一個 Activity 內(nèi)容的容器,是區(qū)分不同的 Activity 界面的最小單位哥遮,也是 PhoneWindow 的一個實例岂丘,Activity 加載過程中調(diào)用
attach
方法的時候被實例化; - 頂級 View :如果說 Window 知識一個容器,那么從 mDecor 開始就能夠真的顯示一些東西了眠饮,這是一個 DecorView 對象奥帘,定義在 PhoneWindow 中,用來加載包括狀態(tài)欄和導(dǎo)航欄在內(nèi)的所有布局君仆;
- 系統(tǒng)狀態(tài)欄:statusbar翩概,PhoneWindow 會加載其背景進行占位,但并不具體繪制其內(nèi)容返咱;
- 導(dǎo)航欄:navigationbar钥庇,與狀態(tài)欄一樣,PhoneWindow 會加載其背景進行占位咖摹,但并不具體繪制其內(nèi)容评姨;
- mContentParent:這個就是可以經(jīng)由我們自由添加和修改的布局了,其中可以添加和隱藏標(biāo)題欄萤晴,而如果沒有標(biāo)題欄吐句,大小就會跟 mDecor 相同;
而 setContentView 的過程店读,就是實例化 mWindow嗦枢、mDecor墓赴、mContentParent 的過程悼嫉,在這個過程中,根據(jù) window 的主題坷备、屬性特征的不同殖演,會加載不同的布局和 UI氧秘,包括是否顯示狀態(tài)欄、導(dǎo)航欄甚至是標(biāo)題欄趴久。經(jīng)過這個過程丸相,一個基本的 Activity 就可以顯示出來,在這個基礎(chǔ)上彼棍,我們就可以去按照自己的需求對 mContentParent 進行定制來充實自己的 App灭忠。
本文最早發(fā)布于alphagao,com膳算。