在activity中调用setContentView,传入xml文件,就会有相应的ui呈现出来,是不是感觉很简单,调用setContentView一个方法就轻松实现了,其实setContentView底层是做了很多处理,接下来就一层一层点进去,看下setContentView的源码;
setContentView.png
点进activity的源码找到setContentView()方法,
/**
* 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) {
//调用Window中的setContentView
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()获取到的是一个Window对象,调用的是Window中的setContentView()方法,
public abstract void setContentView(@LayoutRes int layoutResID);
会看到Window中的setContentView()方法是一个抽象方法,应该找Window的子类中的setContentView();方法,PhoneWindow是Window子类,找到PhoneWindow中的setContentView();方法
@Override
public void setContentView(int layoutResID) {
// mContentParent 就是一个ViewGroup 首次加载的时候mContentParent 肯定是空的,空的话就会调用 installDecor()方法,可以获取到一个DecorView对象
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 {
//通过inflate解析加载布局资源
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
mContentParent是一个ViewGroup,在首次创建activity时,mContentParent是为空的,就会调用installDecor();方法,
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//获取到一个DecorView 首次加载的时候DecorView对象是空的,调用generateDecor()方法进行初始化
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
//DecorView不为null时,通过DecorView设置Window
mDecor.setWindow(this);
}
//给mContentParent赋值
if (mContentParent == null) {
//这里的mContentParent对象还是空的,根据上面初始化获取的DecorView对象并调用generateLayout()方法,获取ViewGroup
mContentParent = generateLayout(mDecor);
...
} else {
...
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
....
}
}
}
这里的mContentParent对象还是空的,根据上面初始化获取的DecorView对象并调用generateLayout()方法,获取ViewGroup
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;
} 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!");
}
mDecor.startChanging();
//调用DecorView中的onResourcesLoaded(),利用LayoutInflater去加载布局
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//通过findViewById获取对应的ViewGroup,并将ViewGroup返回,到这里mContentParent就已经初始化好了,
//ID_ANDROID_CONTENT这个id其实就是com.android.internal.R.id.content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
根据上面获取到的layoutResource布局资源和LayoutInflater对象调用DecorView中的onResourcesLoaded()方法从而加载布局,这里需要注意的是layoutResource是系统的布局资源id,并不是在activity中setContentView时设置的布局资源id
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
mDecorCaptionView = createDecorCaptionView(inflater);
//利用LayoutInflater对象中的inflate方法进行布局文件的加载,这里加载的是一个系统资源布局,并返回一个View对象
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
//DecorCaptionView不为空的话,就调用DecorCaptionView中的addView()方法将解析的View添加进去
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
//如果DecorCaptionView对象为空的话,就调用ViewGroup中的addView()方法,将解析的View添加进去
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
同过上面这些就将系统资源布局添加进去了,在这里是采用LayoutInflater来解析获取到的布局文件并返回一个View对象;在fragment中或者很多时候要加载布局的时候,都会用到LayoutInflater,经常使用的有三种加载布局的方式;
View.inflate(Context context, @LayoutRes int resource, ViewGroup root);
LayoutInflater.from(Context mContext).inflate(@LayoutRes int resource, @Nullable ViewGroup root);
LayoutInflater.from(Context mContext).inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);
先看下这个三种加载方式各自的源码;
View.inflate:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
LayoutInflater.from:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
在用后面两种方式的时候,首先要调用LayoutInflater中的from方法,返回的是一个LayoutInflater
/**
* Obtains the LayoutInflater from the given context.
*/
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
点击去发现Context中的getSystemService又是一个抽象方法,
public abstract Object getSystemService(@ServiceName @NonNull String name);
那就只能去找Context的子类ContextImpl了,找到ContextImpl中的getSystemService();
@Override
public String getSystemServiceName(Class<?> serviceClass) {
return SystemServiceRegistry.getSystemServiceName(serviceClass);
}
到这里又涉及到SystemServiceRegistry,这类里面注册了很多服务;第一种和第二种方式往里面看的时候其实调用的都是第三种方式,
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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
在这里可以获取到一个XmlResourceParser解析器去解析xml文件,
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
try {
...
if (DEBUG) {
...
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
...
}
} catch (XmlPullParserException e) {
...
} catch (Exception e) {
...
} finally {
...
}
return result;
}
}
找到createViewFromTag点进去,
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
try {
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
...
} catch (ClassNotFoundException e) {
...
} catch (Exception e) {
...
}
}
对xml布局文件解析完成后会继续走onResourcesLoaded(LayoutInflater inflater, int layoutResource)方法中的代码,会根据DecorCaptionView对象是否为空,来addView,如果DecorCaptionView对象为空,调用的是ViewGroup中的addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))方法,
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
//调用requestLayout进行布局摆放
requestLayout();
//调用invalidate进行view绘制渲染
invalidate(true);
addViewInner(child, index, params, false);
}
系统布局解析完成后,将会将解析得到的view通过addView方法添加到DecorView布局中,在addView中就会去调用requestLayout进行摆放和invalidate进行绘制和渲染,在调用requestLayout时就会去调用View中的requestLayout方法;
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
//获取ViewRootImpl实例,View的绘制流程大部分都是在ViewRootImpl类中完成的,
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
//ViewParent是一个接口,ViewGroup和ViewRootImpl是它的实现类,就要去看ViewRootImpl类中的requestLayout方法
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
通过一步步调用就到了ViewRootImpl中的requestLayout方法中,在该方法中首先会进行线程的检查,接着会去绘制渲染view;具体可以看invalidate和postInvalidate使用场景及源码解析
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查当前线程是否是ui线程,如果不是ui线程会抛异常
checkThread();
mLayoutRequested = true;
//进行页面的绘制和渲染
scheduleTraversals();
}
}
到这里的话,其实还只是将系统的布局资源添加到DecorView中,并没有将activity中setContentView的布局资源添加到系统中;接着看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;
}
上面走了installDecor()方法实例化了一个DecorView,对mContentParent进行了赋值,并将mContentParent添加到初始化好的DecorView中,在mLayoutInflater.inflate(layoutResID, mContentParent);这句代码执行后才将setContentView的资源布局添加到mContentParent系统布局中。
总结:在activity中调用setContentView后,会去调用PhoneWindow中的setContentView方法,在setContentView方法中通过installDecor()方法实例化一个DecorView,对mContentParent进行赋值并添加到实例化好的DecorView中,接着通过mLayoutInflater.inflate将setContentView设置的布局资源添加到mContentParent系统布局中。
网友评论