由setContentView()方法引起的思考

Android的setContentView()方法我們平時(shí)用很多冤寿,但是有多少人會(huì)點(diǎn)進(jìn)setContentView()方法里面看看它的源碼究竟是何方神圣呢馏慨,今天我就來(lái)看看從這個(gè)方法里面究竟涉及到多少未知的知識(shí)夷陋。

public class ViewActivity extends Activity {


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view);
    }
}

懷著好奇心我點(diǎn)下了setContentView()這個(gè)方法去尋根索源:

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

getWindow().setContentView(layoutResID)這是什么鬼饥臂,然后點(diǎn)進(jìn)去看看:

image

我的天夺刑,竟然看到setContentView()是一個(gè)叫Window類的抽象方法愉棱,Window相信每個(gè)人都聽(tīng)過(guò)魂莫,但是對(duì)于Android的Window相信不是所有人都了解还蹲,我也是一樣,然后我?guī)е鴨?wèn)題翻閱了書(shū)本耙考。

Window

摘自來(lái)自《Android開(kāi)發(fā)藝術(shù)探索》的解釋:

Window表示一個(gè)窗口的概念谜喊,在日常開(kāi)發(fā)中直接接觸Window的機(jī)會(huì)并不多,但是在某些特殊時(shí)候我們需要在桌面上顯示一個(gè)類似懸浮窗的東西倦始,那么這種效果就需要用到Window來(lái)實(shí)現(xiàn)斗遏。Window是一個(gè)抽象類,他的具體實(shí)現(xiàn)是PhoneWindow鞋邑。創(chuàng)建一個(gè)Window是很簡(jiǎn)單的事诵次,只需要通過(guò)WindowManager即可完成账蓉。WindowManager是外界訪問(wèn)Window的入口,Window的具體實(shí)現(xiàn)位于WindowManagerService中逾一,WindowManager和WindowManagerService的交互是一個(gè)IPC過(guò)程铸本。Android所有的視圖都是通過(guò)Window來(lái)呈現(xiàn)的,不管是Activity遵堵、Dialog還是Toast箱玷,他們的視圖實(shí)際上都是附加在Window上的,因此Window實(shí)際上是View的直接管理者陌宿。

IPC:Inter-Process Communication的縮寫(xiě)锡足,含義為進(jìn)程間通信或者跨進(jìn)程通信,是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過(guò)程壳坪。

看了一大輪的文字概念舶得,我就想睡覺(jué)了。但是看了那么久弥虐,總算幾個(gè)關(guān)鍵詞PhoneWindow扩灯,WindowManagerWindowManagerService。上面講到PhoneWindow是Window的實(shí)現(xiàn)類霜瘪,那么我們先去看看PhoneWindow吧珠插。

@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();
        }

        ...
    }

在PhoneWindow確實(shí)找到了setContentView()方法的具體實(shí)現(xiàn)。
mContentParent是什么颖对?

ViewGroup mContentParent;

暫時(shí)還不知道它是什么捻撑,那我們當(dāng)它是null吧,進(jìn)入installDecor()方法看看:

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }

                ...
                下面省略了一大堆UiOptions缤底,setIcon顾患,Transition的方法
            } else {
                ...
            }

            
    }

mDecor是什么?

private DecorView mDecor;

DecorView是什么个唧?
書(shū)本是這樣寫(xiě)的:
ViewRoot對(duì)應(yīng)于ViewRootImpl類江解,它是連接WindowManagerDecorView的紐帶,View的三大流程(onMeasure(),onLayout(),onDraw())均是通過(guò)ViewRoot來(lái)完成的徙歼。在ActivityThread中犁河,當(dāng)Activity對(duì)象被創(chuàng)建完畢后,會(huì)將DecorView添加到Window中魄梯,同時(shí)會(huì)創(chuàng)建ViewRootImpl對(duì)象桨螺,并將ViewRootImpl對(duì)象和DecorView建立關(guān)聯(lián)

我真的醉了酿秸,越翻越多自己不懂的概念出來(lái):ViewRoot灭翔,ViewRootImpl,現(xiàn)在暫且做個(gè)筆記吧,先不管了辣苏。先看看我們找到的線索:

當(dāng)Activity對(duì)象被創(chuàng)建完畢后肝箱,會(huì)將DecorView添加到Window中.

這就是我們要找的東西哄褒。DecorView原來(lái)是這樣用的。

回到installDecor()中狭园,當(dāng)mDecor為空時(shí)读处,調(diào)用generateDecor(-1)方法:

protected DecorView generateDecor(int featureId) {
        ...
        return new DecorView(context, featureId, this, getAttributes());
    }

這里創(chuàng)建了一個(gè)DecorView了,我們發(fā)現(xiàn)DecorView其實(shí)是一個(gè)FrameLayout唱矛,再回到installDecor(),這時(shí)候我們知道m(xù)ContentParent仍然為null井辜,那么進(jìn)入generateLayout(mDecor)方法:

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //根據(jù)當(dāng)前設(shè)置的主題來(lái)加載默認(rèn)布局
        TypedArray a = getWindowStyle();
        ...
        設(shè)置各種各樣的屬性
        ...
        
        //如果你在theme中設(shè)置了window_windowNoTitle绎谦,則這里會(huì)調(diào)用到,其他方法同理粥脚,
        //這里是根據(jù)你在theme中的設(shè)置去設(shè)置的
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            
            requestFeature(FEATURE_ACTION_BAR);
        }
       
        //是否有設(shè)置全屏
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }

        ...

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        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();
        //選擇對(duì)應(yīng)布局創(chuàng)建添加到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
    }

首先generateLayout會(huì)根據(jù)當(dāng)前用戶設(shè)置的主題去設(shè)置對(duì)應(yīng)的Feature窃肠,接著,根據(jù)對(duì)應(yīng)的Feature來(lái)選擇加載對(duì)應(yīng)的布局文件刷允,(Window.FEATURE_NO_TITLE)接下來(lái)通過(guò)getLocalFeatures來(lái)獲取你設(shè)置的feature冤留,進(jìn)而選擇加載對(duì)應(yīng)的布局,這也就是為什么我們要在setContentView之前調(diào)用requesetFeature的原因树灶。

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

我們還能看到contentParent其實(shí)是一個(gè)叫com.android.internal.R.id.content的布局纤怒,最后添加到DecorView上。generateLayout()方法最后返回的就是contentParent天通。好了泊窘,installDecor()方法走完了,創(chuàng)建了DecorView和在上面設(shè)置了一大堆屬性像寒,并創(chuàng)建帶來(lái)了一個(gè)mContentParent烘豹,再回到PhoneWindow的setContentView()中

@Override
    public void setContentView(int layoutResID) {
        
        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 {
            //把mContentParent加載到mLayoutInflater中,
            //而mLayoutInflater在上面generateLayout(DecorView decor)方法中
            //早已加載到DecorView中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //回調(diào)通知表示完成界面改變
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

此時(shí)已經(jīng)創(chuàng)建完DecorView并且獲取到mContentParent诺祸,接著就是將你setContentView的內(nèi)容添加到mContentParent中携悯,也就是

 mLayoutInflater.inflate(layoutResID, mContentParent);
 或者
 mContentParent.addView(view, params);

來(lái)到這里該總結(jié)一下了:

image

真是千言萬(wàn)語(yǔ)都在這圖中了,網(wǎng)上盜的圖真是萬(wàn)能的筷笨,我的總結(jié)都在這個(gè)圖中了憔鬼。
我以前一直不懂為什么setContentView()叫setContentView而不叫setView呢,那是因?yàn)槲覀兯鶆?chuàng)建的布局其實(shí)是Activity里面的PhoneWindow創(chuàng)建出來(lái)的DecorView里面的ContentView來(lái)的而已奥秆。一開(kāi)始以為你是老大逊彭,現(xiàn)在才發(fā)現(xiàn)你是個(gè)小弟大概就是這種感覺(jué)吧。

等等构订,雖然知道setContentView()方法是怎么來(lái)的侮叮,但是在看它的源碼中,我們還發(fā)現(xiàn)了好幾個(gè)疑問(wèn):WindowManager悼瘾,ViewRoot囊榜,ViewRootImpl审胸,PhoneWindowWindowManagerService卸勺。他們幾個(gè)的關(guān)系又是怎么個(gè)錯(cuò)綜復(fù)雜呢砂沛?拿著這些線索,我們下一篇文章再來(lái)探個(gè)究竟吧曙求。

我的掘金:
https://juejin.im/user/594e8e9a5188250d7b4cd875/posts

我的簡(jiǎn)書(shū):
http://www.reibang.com/u/b538ca57f640

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末碍庵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子悟狱,更是在濱河造成了極大的恐慌静浴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挤渐,死亡現(xiàn)場(chǎng)離奇詭異苹享,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)浴麻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門得问,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人软免,你說(shuō)我怎么就攤上這事宫纬》逅瑁” “怎么了良蛮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)该编。 經(jīng)常有香客問(wèn)我向抢,道長(zhǎng)认境,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任挟鸠,我火速辦了婚禮叉信,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艘希。我一直安慰自己硼身,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布覆享。 她就那樣靜靜地躺著佳遂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撒顿。 梳的紋絲不亂的頭發(fā)上丑罪,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼吩屹。 笑死跪另,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的煤搜。 我是一名探鬼主播免绿,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼擦盾!你這毒婦竟也來(lái)了嘲驾?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤厌衙,失蹤者是張志新(化名)和其女友劉穎距淫,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體婶希,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年蓬衡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喻杈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狰晚,死狀恐怖筒饰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情壁晒,我是刑警寧澤瓷们,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站秒咐,受9級(jí)特大地震影響谬晕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜携取,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一攒钳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雷滋,春花似錦不撑、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至澳泵,卻和暖如春实愚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工爆侣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留萍程,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓兔仰,卻偏偏與公主長(zhǎng)得像茫负,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乎赴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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