Android-setContentView與findViewById源碼解析

原創(chuàng)-轉(zhuǎn)載請(qǐng)注明出處。

當(dāng)我們給Activity設(shè)置布局時(shí)骏庸,都是直接調(diào)用setContentView來完成的戚啥,但具體Android是怎么把布局加載到window,又是怎么通過findViewById獲取view對(duì)象的宛畦,我們可能并沒有太關(guān)心瘸洛,下面就結(jié)合源碼來分析下這個(gè)過程。

Android setContentView

打開Activity的源碼發(fā)現(xiàn)次和,setContentView有三個(gè)重載方法反肋,

  1. public void setContentView(int layoutResID);
  2. public void setContentView(View view);
  3. public void setContentView(View view, ViewGroup.LayoutParams params)
    我們就來看下最常用的第一個(gè)方法:
    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

這個(gè)方法調(diào)用了,Window類中的setContentView()方法,其他方法也是調(diào)用了Window類中的setContentView()踏施,但是Window是一個(gè)抽象類石蔗,在Activity的attach方法中被初始化,其實(shí)是一個(gè)PhoneWindow實(shí)例畅形,所以這個(gè)setContentView方法在PhoneWindow中實(shí)現(xiàn)养距。

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

首先判斷mContentParent是否為空,如果為空的話則調(diào)用installDecor()方法日熬,其次判斷是否設(shè)置了FEATURE_CONTENT_TRANSITIONS屬性棍厌,如果沒有的話則移除所有view(從這里我們可以得出setContentView可以調(diào)用多次,反正會(huì)removeAllViews)竖席,然后調(diào)用LayoutInflater.inflate(),將我們?cè)O(shè)置的布局文件添加到mContentParent中耘纱。接著獲取了一個(gè)Callback對(duì)象,那這個(gè)是在Activity的attach方法中設(shè)置的一個(gè)回調(diào)

mWindow.setCallback(this);  

所以可以得出在Activity中一定有一個(gè)onContentChanged回調(diào)毕荐,我們來看下這個(gè)回調(diào)

public void onContentChanged() {}    

額揣炕,空空如也。但是我們可以在自己的Activity中重寫這個(gè)回調(diào)东跪,用于在setContentView之后做一些事情畸陡,比如findViewById,但貌似實(shí)際場景也不需要鹰溜。。丁恭。
好了曹动,現(xiàn)在我們回到上面提到的installDecor()方法,好長牲览,我們撿重要的看吧墓陈。

private void installDecor() {
             //初始化decorView
            if (mDecor == null) {
                mDecor = generateDecor();
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            }
            //初始化mContentParent
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);
                  ......
                  //設(shè)置一堆屬性值
            }
    }    

看下PhoneWindow中的generateDecor()方法

 protected DecorView generateDecor() {
           return new DecorView(getContext(), -1);
     }      

只是單純的new了一個(gè)DecorView實(shí)例。這個(gè)DecorView是什么鬼第献。其實(shí)它是PhoneWindow的一個(gè)內(nèi)部類贡必,是整個(gè)window界面最頂層的view。包含ActionBar,內(nèi)容塊等庸毫。好了仔拟,現(xiàn)在我們縷一下Window,PhoneWindow,decorView的關(guān)系

1.Window類是一個(gè)抽象類飒赃,提供了繪制窗口的一組通用API利花。可以將之理解為一個(gè)載體载佳,各種View在這個(gè)載體上顯示炒事。
2.PhoneWindow是Window的一個(gè)子類,是Window的具體實(shí)現(xiàn)蔫慧,包含一個(gè)內(nèi)部類DecorView,PhoneWindow是將decorView進(jìn)行了一定包裝挠乳,并提供一些方法用于操作窗口。
3姑躲。DecorView繼承自FrameLayout,是窗口的根view睡扬。

好了,接著看mContentParent的初始化肋联,generateLayout(mDecor).這里傳入了上一部初始化好的DecorView. 又是一個(gè)長方法,我們還是挑出重要的部分刁俭。

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();

        //......
        //根據(jù)定義的style設(shè)置一些值橄仍,比如是否顯示ActionBar,

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        //......
        //根據(jù)設(shè)定好的features值選擇不同的窗口修飾布局文件,
        //得到layoutResource值,系統(tǒng)定義了不同的layout牍戚,比如  
        //R.layout.screen_custom_title,R.layout.screen_simple

        //把選中的窗口修飾布局文件添加到DecorView對(duì)象里侮繁,并且指定contentParent值
        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);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        //......
        //繼續(xù)一堆屬性設(shè)置,返回contentParent
        return contentParent;
    }    

根據(jù)不同的features值,設(shè)定layoutResource如孝,最終添加到decorView中宪哩,所以我們通過在xml中設(shè)置的theme,還有在代碼中設(shè)置的requestWindowFeature,都是用來設(shè)置features值第晰,這也是為什么requestWindowFeature方法必須在setContentView之前的原因锁孟。
這樣看來彬祖,如果我們?cè)O(shè)置我們的Theme為NoTitleBar,最終layoutResource的值為R.layout.screen_simple

    <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>  

來看下去處標(biāo)題欄后的視圖樹

setContentView

所以installDecor主要是初始化了PhoneWindow中的DecorView.和contentParent,之后在setContentView()中通過mLayoutInflater.inflate(layoutResID, mContentParent);將layoutResId,add到初始化好的contentParent中。
大家是否好奇狀態(tài)欄怎么被加載進(jìn)DecorView的品抽,我們來看下DecorView中的updateColorViewInt方法

    private View updateColorViewInt(View view, int sysUiVis, int systemUiHideFlag,
                int translucentFlag, int color, int height, int verticalGravity,
                String transitionName, int id, boolean hiddenByWindowFlag) {
              ......
            if (view == null) {
                if (show) {
                    view = new View(mContext);
                    view.setBackgroundColor(color);
                    view.setTransitionName(transitionName);
                    view.setId(id);
                    addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, height,
                            Gravity.START | verticalGravity));
                }
            } else {
               ......
                   }
            return view;
        }

可以看到直接new了一個(gè)view储笑,這個(gè)view就是狀態(tài)欄,然后將狀態(tài)欄添加到了DecorView,其實(shí)這個(gè)狀態(tài)欄只是一個(gè)單純的占位view圆恤。被updateColorViews方法調(diào)用突倍,比如當(dāng)我們調(diào)用setStatusBarColor時(shí)就是調(diào)用了updateColorViews這個(gè)方法。這里先不做過多介紹盆昙。

findViewById

那么將layout添加進(jìn)decorView中后羽历,我們是怎么通過findViewById找到View的呢?
看下Activity的findViewById方法

        /**
         * Finds a view that was identified by the id attribute from the XML that
         * was processed in {@link #onCreate}.
         *
         * @return The view if found or null otherwise.
         */
        public View findViewById(int id) {
            return getWindow().findViewById(id);
        }       

又是到了window中淡喜,看下window中的方法

    public View findViewById(int id) {
        return getDecorView().findViewById(id);
    }  

是調(diào)用了getDecorView的findViewById秕磷,也就是調(diào)用了view的findViewById,我們來看下view類中

    public final View findViewById(int id) {
        if (id < 0) {
            return null;
        }
        return findViewTraversal(id);
    }
    
    protected View findViewTraversal(int id) {
        if (id == mID) {
            return this;
        }
        return null;
    }  

到這我們就疑惑了,直接判斷了id是否為view的id拆火,是的話就返回跳夭。怎么也應(yīng)該有一個(gè)循環(huán)或者遞歸查找啊,什么都沒有们镜。
這時(shí)我們來看下币叹,mID是怎么初始化的

    ....
    case com.android.internal.R.styleable.View_id:
                    mID = a.getResourceId(attr, NO_ID);
                    break;
   ...  

喔,這個(gè)id就是我們?cè)趚ml中設(shè)置的id模狭。那會(huì)不會(huì)在ViewGroup中進(jìn)行查找的呢颈抚?來看下

    protected View findViewTraversal(int id) {
        if (id == mID) {
            return this;
        }

        final View[] where = mChildren;
        final int len = mChildrenCount;

        for (int i = 0; i < len; i++) {
            View v = where[i];

            if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
                v = v.findViewById(id);

                if (v != null) {
                    return v;
                }
            }
        }

        return null;
    }  

果然, ViewGroup重寫了View的findViewTraversal()方法嚼鹉,遍歷了自己的child的findViewById方法贩汉,如果找到了返回View自身。
ok,到現(xiàn)在我們就理解了view是怎么findViewById的了锚赤。

總結(jié)

上面我們介紹了匹舞,Activity setContentView和findViewById的流程,是不是又多了一層理解呢线脚,喜歡的話就點(diǎn)個(gè)贊吧~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赐稽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子浑侥,更是在濱河造成了極大的恐慌姊舵,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寓落,死亡現(xiàn)場離奇詭異括丁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)伶选,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門史飞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尖昏,“玉大人,你說我怎么就攤上這事祸憋』嵯埽” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵蚯窥,是天一觀的道長掸鹅。 經(jīng)常有香客問我,道長拦赠,這世上最難降的妖魔是什么巍沙? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮荷鼠,結(jié)果婚禮上句携,老公的妹妹穿的比我還像新娘。我一直安慰自己允乐,他們只是感情好矮嫉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著牍疏,像睡著了一般蠢笋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鳞陨,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天昨寞,我揣著相機(jī)與錄音,去河邊找鬼厦滤。 笑死援岩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掏导。 我是一名探鬼主播享怀,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼趟咆!你這毒婦竟也來了添瓷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤忍啸,失蹤者是張志新(化名)和其女友劉穎仰坦,沒想到半個(gè)月后履植,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體计雌,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年玫霎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凿滤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妈橄。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖翁脆,靈堂內(nèi)的尸體忽然破棺而出眷蚓,到底是詐尸還是另有隱情,我是刑警寧澤反番,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布沙热,位于F島的核電站,受9級(jí)特大地震影響罢缸,放射性物質(zhì)發(fā)生泄漏篙贸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一枫疆、第九天 我趴在偏房一處隱蔽的房頂上張望爵川。 院中可真熱鬧,春花似錦息楔、人聲如沸寝贡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晾剖。三九已至警检,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背羡玛。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留账阻,地道東北人符相。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像褒链,于是被迫代替她去往敵國和親唁情。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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