Android FrameWork之旅 --- setContentView

基于Anroid7.0源码 个人笔记

一、平时写的比较多的也是见的比较多的:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //最简单的理解就是把自己定义的布局文件显示到对于的Activity
        setContentView(R.layout.activity_main);
    }
}

二、跟着源码一探究竟:

 /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);//1
        initWindowDecorActionBar();//2
    }

可以看到Google官方给出的解释是:
从布局中设置Activity的内容,该资源将被解析,并且添加的这个Activity的试图顶层。
这个方法大致做了两件事:
1、把资源设置给Window,依靠Window来帮忙显示
2、初始化actionBar等
getWindow()方法返回的是Window,而Window是一个抽象类,具体的实现是在它的子类也就是PhoneWindow.java之中。

 @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) {
            //2.1
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        //2.2
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
        //2.3
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        //2.4
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
        //2.5
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

if (mContentParent == null),那么去初始化mContentParent ,否则就把mContentParent 的子view全部删除,那么mContentParent 是一个ViewGroup。然后把我们最初定义的layout解析并添加的mContentParent 中,最后通过cb回调onContentChanged()方法。
2.1 installDecor()
代码很多,摘取关键部分:

private void installDecor() {  
            if (mDecor == null) {  
                mDecor = generateDecor();  
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);  
                mDecor.setIsRootNamespace(true);  
                //...  
                }  
            }  
            if (mContentParent == null) {  
                mContentParent = generateLayout(mDecor);  
                mTitleView = (TextView)findViewById(com.android.internal.R.id.title);  
                if (mTitleView != null) {  
                   //根据FEATURE_NO_TITLE隐藏,或者设置mTitleView的值  
                    //...  
                } else {  
                    mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);  
                    if (mActionBar != null) {  
                        //设置ActionBar标题、图标神马的;根据FEATURE初始化Actionbar的一些显示  
                        //...  
                    }  
                }  
            }  
    }  

在初始化mContentParent 之前也初始化了一个mDecor ,mDecor 作为初始化mContentParent 的参数传入。mDecor是什么?
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //啪啪啪,省略茫茫多代码。
        ***
        //获取设置各种属性和flag,比如 mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
        //从这里也可以看出,我们为什么要在setContentView方法之前设置对应的flag的原因
        ***

        // Inflate the window decor.
        int layoutResource;
        int features = getLocalFeatures();

        ***
        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

        ***
        //mDecor各种set

        mDecor.finishChanging();
        return contentParent;

}

mDecor.startChanging();

 void startChanging() {
        mChanging = true;
 }

这个方法很简单,就是重置了应该是标志位的东西mChanging

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);这一步猜测就是把布局添加的mDecor之中,看下具体的实现:

 void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ***
        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

inflate layoutResource然后把他add到DecorView之中。到这里mContentParent 的初始化工作已经做完了,并且已经add到DecorView之中。最后调用mDecor.finishChanging()方法:

void finishChanging() {
        mChanging = false;
        drawableChanged();
}

重置mChanging 为false,还调用了drawableChanged(),看下这个方法做了些什么?

private void drawableChanged() {
        if (mChanging) {
            return;
        }
        setPadding(mFramePadding.left + mBackgroundPadding.left,
                mFramePadding.top + mBackgroundPadding.top,
                mFramePadding.right + mBackgroundPadding.right,
                mFramePadding.bottom + mBackgroundPadding.bottom);
        requestLayout();
        invalidate();
        ***
        mDefaultOpacity = opacity;
        if (mFeatureId < 0) {
            mWindow.setDefaultWindowFormat(opacity);
        }
    }

设置padding并刷新视图
回到最开始的地方初始化mContentParent之后:
mLayoutInflater.inflate(layoutResID, mContentParent);
这里就很好理解了,就是把我们定义的layout解析并添加到mContentParent之中,因为前面初始化mContentParent的时候已经把它添加到了DecorView之中,并且设置好了各种属性。
最后看下cb.onContentChanged(),那么cb是什么呢?
final Callback cb = getCallback();
该方法的实现是在PhoneWindow的父类Window之中:

    /**
     * Return the current Callback interface for this window.
     */
    public final Callback getCallback() {
        return mCallback;
    }

既然是getCallback,那么肯定有setCallback,setCallback的调用是在Activity的attach方法中:

  final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        //传入的是Activity本身
        mWindow.setCallback(this);
        ***
}

那么Activity肯定是实现了Callback接口的,果然:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback,

那么看下Activity的onContentChanged方法:

public void onContentChanged() {
}

这是一个空实现,所以开发者可以重写该方法,在这里做一些工作。这个方法目前还没有用过,不知道具体可以干啥。

最后总结一下

1、初始化mContentParent,并且设置各种属性,同时把mContentParent添加到DecorView中
2、解析定义的layout,add到mContentParent中
3、回调Activity的onContentChanged方法

ps:RNG输了,有点伤心,小狗好好养伤,明年的赛场期待再次看到你的vn。

猜你喜欢

转载自blog.csdn.net/lj527409/article/details/78387888