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

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