Android-Activity中setContentView流程解析

在Activity中加載佈局的時候,我們都知道調用的是setContentView方法,那麼具體是如何實現的呢?
本文基於參考其他人博客以及自己翻閱源碼做一個記錄。

隨便找到一個Activity,點擊setContentView方法可以看到實現:

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

layoutResID是我們傳入的佈局id,所以我們先主要關注1處。可以看到這裏調用了getWindow().setContentView,點進去getWindow看一下:

public Window getWindow() {
        return mWindow;
    }

獲取的是一個Window對象,找到Window類,它是一個抽象類,在類註釋中我們可以看到下面一段:

	The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.

可以看到,Window唯一的實現類就是PhoneWindow。再點進去PhoneWindowsetContentView方法:

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    //...
    //...
    //...
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
  
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mContentParent.addView(view, params);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

    //...
    //...
    //...
}

這裏我們關注setContentView(int layoutResID)實現。
mContentParent不爲空時,會先移除其上所以子view,然後通過mLayoutInflater.inflate(layoutResID, mContentParent)將我們的佈局文件加載上去,當mContentParent爲空時,會先調用installDecor()方法,click it:

private void installDecor() {
            if (mDecor == null) {
                mDecor = generateDecor();
                //...
                }
            }
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);

                        //...
                    }
                }
            }
    }

只看主流程代碼,邏輯很清晰,mContentParent爲空的時候,會執行generateLayout()方法,同時需要傳入一個mDecor,那麼mDecor是什麼東西呢,我們可以點進generateDecor方法看一下:

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

嘿,又出來了一個DecorView,這是個什麼東西呢?查看它的源碼:

private final class DecorView extends FrameLayout {}

可以看到,DecorView繼承FrameLayout,是一個ViewGroup。回到前面的generateLayout(mDecor),看一下我們往DecorView裏都有什麼東西:

public class PhoneWindow extends Window implements MenuBuilder.Callback {

    protected ViewGroup generateLayout(DecorView decor) {
        // 此處省去一堆代碼,設置窗口屬性

        int layoutResource;
        // 此處省去一堆代碼,根據不同的主題使用不同的佈局資源

        // 這裏纔是重點,向 DecorView 添加布局,並且從 DecorView 中查找出 contentParent
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        // 此處省去一堆代碼設置窗口背景和標題
        
        return contentParent;
    }
    
}

在這個方法裏,我們contentParent其實指向的就是ID_ANDROID_CONTENT,也就是R.id.content。到目前爲止,我們可以用一張圖來概括上邊兒的流程:
setContentView簡易流程圖
找到mContentParent之後,回到setContentView方法中:

// 解析我們設置的佈局資源並且設置 mContentParent 爲父佈局
        mLayoutInflater.inflate(layoutResID, mContentParent);

至此,我們自己的佈局就加載在了Activity上,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章