Activity是android的四大组件之一,其重要性不言而喻,而且在我们的开发过程中打交道最多的也是它。在设置视图的时候,我们一般都是通过setContentView来加载我们的布局资源的,看起来很简单的一行代码setContentView(),但是实际上里面都做了哪些事情你真的知道吗?
在开始讲解setContentView的源码之前,你首先要弄懂上面的这张图,弄不懂也没关系,但是你需要记住它,这样有利于后面的源码理解。
源码解析
@Override
public void setContentView(int layoutResID) {
// mContentParent即DecorView
// mContentParent是否为空,第一次进来的时候为空
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;
}
private void installDecor() {
mForceDecorInstall = false;
// mDecor是否为空,第一次进来的时候为空
if (mDecor == null) {
// 初始化DecorView
mDecor = generateDecor(-1);
// 省略部分代码
} else {
mDecor.setWindow(this);
}
// mContentParent是否为空,第一次进来的时候为空
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// 省略部分代码
}
}
// 初始化DecorView
protected DecorView generateDecor(int featureId) {
// 省略部分代码
// 创建DecorView
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();
// 设置 当前Activity配置的主题theme
// 窗口是否是浮动的
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
// 窗口是否有标题栏
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
// 窗口中ActionBar是否在布局空间内
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
requestFeature(FEATURE_SWIPE_TO_DISMISS);
}
// 窗口是否全屏显示
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
... ...
//窗口状态栏颜色配置
if (!mForcedStatusBarColor) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
//窗口导航栏颜色配置
if (!mForcedNavigationBarColor) {
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
}
if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB) {
if (a.getBoolean(
R.styleable.Window_windowCloseOnTouchOutside,false)) {
setCloseOnTouchOutsideIfNotSet(true);
}
}
WindowManager.LayoutParams params = getAttributes();
//输入法配置
if (!hasSoftInputMode()) {
params.softInputMode = a.getInt(
R.styleable.Window_windowSoftInputMode,
params.softInputMode);
}
if (a.getBoolean(R.styleable.Window_backgroundDimEnabled, mIsFloating)) {
/* All dialogs should have the window dimmed */
if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
}
if (!haveDimAmount()) {
params.dimAmount = a.getFloat(
android.R.styleable.Window_backgroundDimAmount, 0.5f);
}
}
//设置当前Activity的出现动画效果
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
R.styleable.Window_windowAnimationStyle, 0);
}
// The rest are only done if this window is not embedded; otherwise,
// the values are inherited from our container.
if (getContainer() == null) {
if (mBackgroundDrawable == null) {
if (mBackgroundResource == 0) {
mBackgroundResource = a.getResourceId(
R.styleable.Window_windowBackground, 0);
}
if (mFrameResource == 0) {
mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
}
mBackgroundFallbackResource = a.getResourceId(
R.styleable.Window_windowBackgroundFallback, 0);
if (false) {
System.out.println("Background: "
+ Integer.toHexString(mBackgroundResource) + " Frame: "
+ Integer.toHexString(mFrameResource));
}
}
mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
}
}
// 为窗口添加 decor根布局
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.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 = com.android.internal.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(
com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.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(
com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
} else {
layoutResource = com.android.internal.R.layout.screen_action_bar;
}
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = com.android.internal.R.layout.screen_simple;
}
mDecor.startChanging();
// 通过布局填充器LayoutInflater将layoutResource填充为布局,加载到decor上
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
// 找到ID为com.android.internal.R.id.content的ViewGroup布局并进行返回
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
// 设置窗口的背景标题等
if (getContainer() == null) {
Drawable drawable = mBackgroundDrawable;
if (mBackgroundResource != 0) {
drawable = getContext().getResources().getDrawable(mBackgroundResource);
}
mDecor.setWindowBackground(drawable);
drawable = null;
if (mFrameResource != 0) {
drawable = getContext().getResources().getDrawable(mFrameResource);
}
mDecor.setWindowFrame(drawable);
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
if (mTitle != null) {
setTitle(mTitle);
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
在这里先总结一下:
1.我们知道在Activity中设置setContentView()首先会去执行到Activity的setContentView()方法
2.而Activity的setContentView会去执行到Window类里面,而Window里面的setContentView()方法是个抽象方法,最终会执行到Window的具体类PhoneWindow里面的setContentView()方法里面
3.在PhoneWinow里面首先会去判断mContentParent是否为空,那么mContentParent是什么呢?其实mContentParent是DecorView类的根布局,而DecorView是PhoneWindow的一个内部类
4.当mContentParent为空的时候,会去实例化DecorView,同时在实例化DecorView的时候会去设置状态栏的一些属性并且找到一个ID为com.android.internal.R.id.content的ViewGroup布局并进行返回
5.最终执行到setContentView()方法的第16行mLayoutInflater.inflate(layoutResID, mContentParent);其中layoutResID为我们传递进去的布局文件,mContentParent为ID为com.android.internal.R.id.content的ViewGroup
那么mLayoutInflater.inflate(layoutResID, mContentParent);最终会执行到哪里呢?
查看源码知道,最终会执行到LayoutInflater的inflate方法里面
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
// XmlResourceParser是专门用来解析XML文件的
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
// 省略部分代码
// attrs是传入的布局layout在xml文件里面设置的属性集合
final AttributeSet attrs = Xml.asAttributeSet(parser);
// 将ViewGroup类型的参数root赋值给result
View result = root;
// temp是传入的参数resource的根布局View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
// 实例化temp视图内的所有子视图
rInflateChildren(parser, temp, attrs, true);
if (root != null) {
// 根据attrs属性集,创建布局参数params
params = root.generateLayoutParams(attrs);
// 如果temp不需要添加到root中,那么为temp设置布局参数params
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
if (root != null && attachToRoot) {
// 将temp添加到root中,并使用布局参数params
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
// 将temp赋值给result(在此之前,result==root)
result = temp;
}
return result;
}
}
到这里setContentView就分析完了,现在我们回过头来看看setContentView()方法到底做了什么?
其实答案很简单,setContentView()方法只是加载了我们设置的布局文件并对里面的属性进行了解析,其他的事情什么都没做,那么我们设置的布局是如何显示在界面上的呢?
哈哈哈,相信大家都已经猜到了,最终的布局绘制是通过Activity的生命周期来实现的,那么下结我们就一起来分析一下View的绘制流程