前言
我们基本上都是在Activity
的onCreate()
方法中设置setContentView()
,为什么要在onCreate()
中设置呢?setContentView()
是如何起作用的呢?我们今天就简单来说明下,欢迎吐槽指正
源码解析
首先我们来看下基本写法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
为什么在onCreate()
中写setContentView()
这里可能需要稍微了解下Activity的启动模式,看完之后,我们大概就会对整个Activity
启动有所了解,整个流程基本上就是ActivityThread
执行完performLaunchActivity
后,Activity
会回调onAttach
方法,然后Instrumentation
会执行callActivityOnCreate()
方法.然后Activity
执行performCreate()
方法,然后Activity
执行onCreate
方法,然后执行performStart()
方法.接着就是Instrumentation
执行callActivityOnStart()
方法,然后就是Activity
的onStart()
…
最开始执行的是onAttach()
方法,但是它不能被重写,同时布局要越早加载越好,所以最好是在onCreate()
方法中处理,同时官方也有说明
/**
* Called when the activity is starting. This is where most initialization
* should go: calling {@link #setContentView(int)} to inflate the
* activity's UI, using {@link #findViewById} to programmatically interact
* with widgets in the UI, calling
*
* 就是这一小段注释,告诉我们要在这里写setContentView(int)来加载我们自己的布局
* ...
*/
@MainThread
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
这里略过源码
...
}
setContentView()
是如何起作用的
我们先点开这个方法
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
这个getDelegate()
方法返回是一个AppCompatDelegate
类,AppCompatDelegate
是AppCompatActivity
的实现,并且兼容任何Activity
继续点开发现有个它有个实现类AppCompatDelegateImpl
,这个类里面重写了setContentView
方法
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}
@Override
public void setContentView(View v, ViewGroup.LayoutParams lp) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v, lp);
mOriginalWindowCallback.onContentChanged();
}
我们看下ensureSubDecor()
private void ensureSubDecor() {
if (!this.mSubDecorInstalled) {
this.mSubDecor = this.createSubDecor();
CharSequence title = this.getTitle();
if (!TextUtils.isEmpty(title)) {
if (this.mDecorContentParent != null) {
this.mDecorContentParent.setWindowTitle(title);
} else if (this.peekSupportActionBar() != null) {
this.peekSupportActionBar().setWindowTitle(title);
} else if (this.mTitleView != null) {
this.mTitleView.setText(title);
}
}
//适配尺寸
this.applyFixedSizeWindow();
this.onSubDecorInstalled(this.mSubDecor);
this.mSubDecorInstalled = true;
AppCompatDelegateImpl.PanelFeatureState st = this.getPanelState(0, false);
if (!this.mIsDestroyed && (st == null || st.menu == null)) {
this.invalidatePanelMenu(108);
}
}
}
主要就是createSubDecor()
我们继续往下看,里面代码很多,这里我们就找重要的说说
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
通过上面的代码我们发现了在此处进行了大量的requestFeature
的调用,也就是说,我们的requestFeature
设置其实是在setContentView
方法当中就开始了,所以我们设置一些getWindow.requestFeature
时必须在setContentView
之前的原因了
然后里面会有一行代码mWindow.getDecorView();
这个mWindow就是Window
,而且PhoneWindow
是Window
的唯一子类,所以最终就会到PhoneWindow
的setContentView
方法中
@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) {
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
这里的mContentParent
是一个ViewGroup
第一次肯定为空,执行installDecor()
这个方法
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
...
} else {
mTitleView = findViewById(R.id.title);
if (mTitleView != null) {
...
}
}
...
}
}
这里我们只关心重要代码mDecor = generateDecor(-1);
和 mContentParent = generateLayout(mDecor);
第一方法得到的就是顶层的DecorView
, 第二个方法得到的是android.R.id.content
控件FramLayout
我们来看第一个方法源码:
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
这里返回的是一个DecorView
,我们点开这个DecorView
构造方法,发现里面有一个PhoneWindow
,也就是说DecorView
和PhoneWindow
之间具有对应关系,即一个PhoneWindow
对应一个DeocrView
接下来我们再看第二个方法generateLayout(mDecor)
protected ViewGroup generateLayout(DecorView decor) {
...
只看核心的代码
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
... 省略了部分代码
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
... 省略了部分代码
return contentParent;
这个方法得到的得到的就是android.R.id.content
的控件FrameLayout
,即mContentParent
实质是一个FrameLayout
layoutResource
通过DecorView
的onResourcesLoaded()
方法添加到DecorView
自身
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
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();
}
这里我们发现最终都会调用addView
方法,这个rootView没有任何父布局依赖,而是直接以addView
的方式最终加载到DecorView
本身上,DecorView
作为FrameLayout
成为了rootView
的最终父布局。
我们再次回到PhoneWindow
的setContentView()
方法中
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
mLayoutInflater.inflate(layoutResID, mContentParent);
这句话等价于inflate(layoutResID, mContentParent, mContentParent != null),
因为此时mContentParent!= null
,无需执行addView
操作,inflate
会自动将我们传入的Actvity
布局添加在了mContentParent
上了,这样setContentView
的工作就完成了。
总结
一个Activity启动后,首先实例化PhoneWindow
对象,调用setContentView
时,首先执行installDecor()
,通过generateDecor()
实例化一个DecorView
对象,将PhoneWindow
和DecorView
进行了关联绑定,通过generateLayout()
加载系统布局到DecorView
上,并将ID为content
的FrameLayout
赋值给mContentParent
,最后执行inflate()
将我们的布局文件自动添加到mContentParent
。形成一个一一对应关系,如下图: