做Android开发的朋友们都熟悉这个方法: setContentView(R.layout.activity_main);
喜欢思考的朋友们肯定想知道为什么一上来就要执行这个方法呢???
精华所在setContentView 只是创建DecorView 把我们的布局加载到了DecorView
问题剖析我们按住 ctr 并用鼠标右键 setContentView(R.layout.activity_main) 会来到如下源码:
有些人的 MainActivity 可能继承 AppCompatActivity,问题不大,我给大家讲继承 Activity 的,道理是相通的,大家可以自己尝试理解
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* 从布局资源中设置活动内容。 该资源将被inflated,将所有顶层视图添加到活动中。
*
* @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) {
// 获取Window 调用window的setContentView方法,发现是抽象类,所以需要找具体的实现类
// PhoneWindow
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*顶层窗口外观和行为策略的抽象基类。 该类的实例应作为添加到窗口管理器的顶层视图。它提供了标准的UI
*策略,如背景、标题区域、默认键处理等。
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*
*这个抽象类唯一存在的实现是android.view.PhoneWindow,当需要一个Window时,你应该实例化它。
*/
public abstract class Window{
}
当你找到 PhoneWindow 的时候,你发现它并不在 android.view 包里,新版的在 android.internal.policy 下
@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) {
// 如果mContentParent 等于空,调用installDecor();
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 {
// 把我们自己的布局layoutId加入到mContentParent,我们set进来的布局原来是放在这里面的
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
这个时候,我们找到了 setContentView 的根源 ,在里面有 一个方法值的我们关注 installDecor() 于是,我们点进去
// This is the top-level view of the window, containing the window decor.
// 这句话是不是很熟悉
private DecorView mDecor;
private void installDecor() {
if (mDecor == null) {
// 先去创建一个 DecorView
mDecor = generateDecor(-1);
}
// ......
// 省略调一些代码,看着晕,不过这也太省了。
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
// generateDecor 方法
protected DecorView generateDecor(int featureId) {
// 就是new一个DecorView ,DecorView extends FrameLayout 不同版本的源码有稍微的区别,
// 低版本DecorView 是PhoneWindow的内部类,高版本是一个单独的类,不过这不影响。
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
// Inflate the window decor.
// 我看你到底怎么啦
int layoutResource;
// 都是一些判断,发现 layoutResource = 系统的一个资源文件,
if(){}else if(){}else if(){
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
// 把布局解析加载到 DecorView 而加载的布局是一个系统提供的布局,不同版本不一样
// 某些源码是 addView() 其实是一样的
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// ID_ANDROID_CONTENT 是 .content,这个View是从DecorView里面去找的,
// 也就是 从系统的layoutResource里面找一个id是.content的一个FrameLayout
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 返回
return contentParent;
}
上面说了那么多,估计大家思路都有点乱了,下面,我给大家整理一下:
- Activity里面设置setContentView(),其实是调用PhoneWindow中的setContentView(),在PhoneWindow中通过 installDecor() 实例化一个DecorView。(提醒大家,installDecor() 和 setContentView() 都在PhoneWindow 类中,所以有很多 变量 是公用的)
- mDecor = generateDecor(-1); 这句话是 实例化一个DecorView 的关键,
- mContentParent = generateLayout(mDecor); 将 mDecor (实例化一个DecorView)传进去进行一系列的系统加工(这里包括是否有头部等系统加工,便很好解决了为什么要在 setContentView() 之前执行设置Window的Flag或者requestWindowFeature()才会起作用)
- mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);这个就是给 mDecor 添加配置的关键语句,最后通过 return contentParent 返回给 mContentParent(这个是全局变量)