setContentView 背后那些事兒

一行簡單的 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膳算。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市更舞,隨后出現(xiàn)的幾起案子畦幢,更是在濱河造成了極大的恐慌,老刑警劉巖缆蝉,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宇葱,死亡現(xiàn)場離奇詭異,居然都是意外死亡刊头,警方通過查閱死者的電腦和手機黍瞧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來原杂,“玉大人印颤,你說我怎么就攤上這事〈┮蓿” “怎么了年局?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咸产。 經(jīng)常有香客問我矢否,道長,這世上最難降的妖魔是什么脑溢? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任僵朗,我火速辦了婚禮,結(jié)果婚禮上屑彻,老公的妹妹穿的比我還像新娘验庙。我一直安慰自己,他們只是感情好社牲,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布粪薛。 她就那樣靜靜地躺著,像睡著了一般搏恤。 火紅的嫁衣襯著肌膚如雪汗菜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天挑社,我揣著相機與錄音,去河邊找鬼巡揍。 笑死痛阻,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腮敌。 我是一名探鬼主播阱当,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼俏扩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了弊添?” 一聲冷哼從身側(cè)響起录淡,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎油坝,沒想到半個月后嫉戚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡澈圈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年彬檀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞬女。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡窍帝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诽偷,到底是詐尸還是另有隱情坤学,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布报慕,位于F島的核電站深浮,受9級特大地震影響卖子,放射性物質(zhì)發(fā)生泄漏洋闽。R本人自食惡果不足惜诫舅,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一这弧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧匾浪,春花似錦卷哩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苗胀。三九已至柒巫,卻和暖如春堡掏,著一層夾襖步出監(jiān)牢的瞬間应结,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工亭畜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玷坠,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像汰现,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子庐扫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容