04.源碼閱讀(setContentView-api26)

關(guān)鍵詞:PhoneWindow DecorView

在調(diào)用setContentView方法設(shè)置布局的時(shí)候,系統(tǒng)做了什么笼恰?

在AppCompatActivity中

@Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

可以看到AppCompatDelegate中

public abstract void setContentView(@LayoutRes int resId);

是一個(gè)抽象方法
接下來看這個(gè)getDelegate是什么

/**
     * @return The {@link AppCompatDelegate} being used by this Activity.
     */
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

AppCompatDelegate是一個(gè)類似工廠類的抽象類,會根據(jù)sdk版本create不同的子類

/**
     * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code dialog}.
     *
     * @param callback An optional callback for AppCompat specific events
     */
    public static AppCompatDelegate create(Dialog dialog, AppCompatCallback callback) {
        return create(dialog.getContext(), dialog.getWindow(), callback);
    }

    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (BuildCompat.isAtLeastN()) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

我們就是要從這些實(shí)現(xiàn)類中找到setContentView方法
從源碼中可以看到這些子類的繼承關(guān)系

AppCompatDelegateImplN extends AppCompatDelegateImplV23
AppCompatDelegateImplV23 extends AppCompatDelegateImplV14
AppCompatDelegateImplV14 extends AppCompatDelegateImplV11
AppCompatDelegateImplV11 extends AppCompatDelegateImplV9
AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
最終
AppCompatDelegateImplBase extends AppCompatDelegate

我們在AppCompatDelegateImplV9中找到setContentView方法

@Override
    public void setContentView(int resId) {
        //獲取到mSubDecor扔役,這是一個(gè)ViewGroup,在這個(gè)ViewGroup中有一個(gè)id為content的ViewGroup警医,最終我們設(shè)置的layout就是添加到這個(gè)id為content的ViewGroup中的
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

這里先簡單看一下LayoutInflator inflate的源碼亿胸,以后會具體分析,看完這個(gè)我們再分析mSubDecor的獲取

LayoutInflater.from(mContext).inflate(resId, contentParent);

LayoutInflator中

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        //從一個(gè)layout的id中解析出XmlResourceParser
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            //獲取布局的參數(shù)
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            //解析這個(gè)xml布局
            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                //xml解析失敗,沒有找到start tag
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

                ......
                

                //merge標(biāo)簽處理
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        ......
                        // 設(shè)置布局參數(shù)
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {                   
                            temp.setLayoutParams(params);
                        }
                    }
                    ......
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    ......
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        //把view add到root中预皇,inflate方法的作用其實(shí)就是把一個(gè)view添加到一個(gè)ViewGroup中
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            ......

            return result;
        }
    }

我們簡單看下rInflate和rInflateChildren方法

rInflate

/**
     * Recursive method used to descend down the xml hierarchy and instantiate
     * views, instantiate their children, and then call onFinishInflate().
     * <p>
     * <strong>Note:</strong> Default visibility so the BridgeInflater can
     * override it.
     */
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                //添加到布局中
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }
        //當(dāng)布局inflate完成的時(shí)候侈玄,回調(diào)view的onFinishInflate方法,這個(gè)方法就是我們在自定義View時(shí)經(jīng)常重寫的那個(gè)onFinishInflate方法吟温,
        //在這個(gè)方法中為什么獲取不到view的寬高序仙?因?yàn)橹皇遣季謎nflate完成,還沒有進(jìn)行測量鲁豪,onMeasure還沒有開始
        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

rInflateChildren

/**
     * Recursive method used to inflate internal (non-root) children. This
     * method calls through to {@link #rInflate} using the parent context as
     * the inflation context.
     * <strong>Note:</strong> Default visibility so the BridgeInflater can
     * call it.
     */
    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        //最終調(diào)用的還是rInflate方法
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

看到這里我們知道了一些東西潘悼,setContentView方法就是將我們設(shè)置的layout解析成view之后add到了mSubDecor的一個(gè)id為android.R.id.content的ViewGroup中(contentParent)了,然后再次回到setContentView 方法中的ensureSubDecor方法中爬橡,mSubDecor是什么治唤?如何獲取的?

private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();  
            .......
        }
    }
private ViewGroup createSubDecor() {
  
        ......

        // Now let's make sure that the Window has installed its decor by retrieving it
        mWindow.getDecorView();

        ......

        // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);

        ......

        return subDecor;
    }

這里的Window指的是PhoneWindow

mWindow.getDecorView();

//如果mDecor不存在就創(chuàng)建糙申,所以官方注釋說
Now let's make sure that the Window has installed its decor by retrieving it
@Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

installDecor這個(gè)方法宾添,我們關(guān)注兩個(gè)點(diǎn),第一郭宝,它創(chuàng)建了DecorView辞槐,并返回掷漱,
第二粘室,通過DecorView創(chuàng)建了mContentParent

private void installDecor() {
        mForceDecorInstall = false;
        
        if (mDecor == null) {
            //如果mDecor為null,就創(chuàng)建出來
            mDecor = generateDecor(-1);
            ......
        } else {
            //給DecorView設(shè)置Window
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //如果mContentParent為null,就創(chuàng)建出來
            mContentParent = generateLayout(mDecor);
            ........
        }
    }
    //DecorView是被new出來的
    protected DecorView generateDecor(int featureId) {

        ......

        return new DecorView(context, featureId, this, getAttributes());
    }
    
    protected ViewGroup generateLayout(DecorView decor) {
        ......
        //The ID that the main layout in the XML layout file should have.這是一個(gè)系統(tǒng)層面的ViewGroup
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ......
        return contentParent;
    }

源碼new出來了一個(gè)DecorView,然后再根據(jù)具體情況選取一個(gè)系統(tǒng)的布局add到DecorView中卜范,subDecor就是這個(gè)系統(tǒng)布局衔统,這個(gè)布局會被添加到DecorView中,DecorView又是被添加到PhoneWindow上
mWindow.setContentView(subDecor);

@Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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) {
            //確保mContentParent不為null,這里基本上不存在為null的情況,因?yàn)樵?            //mWindow.getDecorView();的時(shí)候如果為 null就會創(chuàng)建出來
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ......
        } else {
            //把subDecor加入了mContentParent
            mContentParent.addView(view, params);
        }
        ......
    }

這樣一個(gè)過程下來锦爵,基本上setContentView的作用有了基本的結(jié)論
這里借用一下一位博主的圖片來說明手機(jī)屏幕的View層級關(guān)系


4314397-a952a39dd0bba2d9.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舱殿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子险掀,更是在濱河造成了極大的恐慌沪袭,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件樟氢,死亡現(xiàn)場離奇詭異冈绊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)埠啃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門死宣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碴开,你說我怎么就攤上這事毅该。” “怎么了潦牛?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵眶掌,是天一觀的道長。 經(jīng)常有香客問我罢绽,道長畏线,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任良价,我火速辦了婚禮寝殴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘明垢。我一直安慰自己蚣常,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布痊银。 她就那樣靜靜地躺著抵蚊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溯革。 梳的紋絲不亂的頭發(fā)上贞绳,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機(jī)與錄音致稀,去河邊找鬼冈闭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抖单,可吹牛的內(nèi)容都是我干的萎攒。 我是一名探鬼主播遇八,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼耍休!你這毒婦竟也來了刃永?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤羊精,失蹤者是張志新(化名)和其女友劉穎斯够,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喧锦,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雳刺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裸违。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掖桦。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖供汛,靈堂內(nèi)的尸體忽然破棺而出枪汪,到底是詐尸還是另有隱情,我是刑警寧澤怔昨,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布雀久,位于F島的核電站,受9級特大地震影響趁舀,放射性物質(zhì)發(fā)生泄漏赖捌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一矮烹、第九天 我趴在偏房一處隱蔽的房頂上張望越庇。 院中可真熱鬧,春花似錦奉狈、人聲如沸卤唉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桑驱。三九已至,卻和暖如春跛蛋,著一層夾襖步出監(jiān)牢的瞬間熬的,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工赊级, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留押框,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓此衅,卻偏偏與公主長得像强戴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子挡鞍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

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

  • 人們喜歡關(guān)注別人的成功,卻喜歡聚焦自己的失敗翘县。 01 我曾經(jīng)失敗至極最域,省城創(chuàng)業(yè),賠個(gè)精光锈麸。出發(fā)前有人說:“那里”水...
    鹿雯立love閱讀 758評論 0 0
  • 落筆長噓短嘆镀脂, 內(nèi)心如浪涌、千層亂忘伞。 又把往昔尋遍薄翅, 未想舊憶當(dāng)拋, 絲連藕斷氓奈。 不求承諾兌現(xiàn)翘魄, 何許永無變。 花...
    龍之風(fēng)閱讀 234評論 7 14
  • 1舀奶、要買也要買在鬧市區(qū)暑竟,蘭大旁邊,別去那些荒郊野外了育勺。 2但荤、要注意用周易來規(guī)劃,否則就是不行涧至。每年開年或者時(shí)候要注...
    智囊團(tuán)閱讀 171評論 0 0
  • 最近很多人說《歡樂頌2》追不下去了化借,各個(gè)網(wǎng)站打分也很低潜慎。但我還在斷斷續(xù)續(xù)地看,甚至潮涂担看得淚流滿面铐炫。 我很樂于看到在...
    馮塵閱讀 356評論 0 2
  • 把錢花在自己身上是一種方法。更重要的是把時(shí)間蒜焊,把注意力放在自己身上倒信。只有這樣才會讓成長的速度達(dá)到最大。 踐行老師提...
    大人黃桃閱讀 156評論 0 0