Activity setContentView源码分析
当我们创建一个Activity,只需要在onCreate方法中,调用setContentView方法,把我们的布局id传进去,系统机会自动帮我们把布局展示出来,那setContentView方法究竟都做了什么工作,还有我们的布局添加进了哪里?
我们今天的问题:
- setContentView的源码都做了哪些工作?
- 我们的布局添加到了哪里?
- 继承Activity和AppCompatActivity有什么不同?
首先,我们以Activity为例。
发现是调用的getWindow的setContentView方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
而Window是abstract类,用来描述Activity视图最顶端的窗口显示和行为操作,通过代码发现,他的实现类是PhoneWidow
mWindow = new PhoneWindow(this, window);
public Window getWindow() {
return mWindow;
}
我们看PhoneWidow中的setContentView方法
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();//创建DecorView--同时创建mContentParent
} 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);//把我们的布局Id通过inflate添加到mContentParent中
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
我们先看installDecor这个方法,这个方法是在mContentParent为null的情况下调用,这里我只抽取了其中两个重要的地方,一个是创建DecorView 一个是创建mContentParent
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
}
....
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
.....
}
generateDecor(-1);方法
protected DecorView generateDecor(int featureId) {
.....
//new DecorView对象。
return new DecorView(context, featureId, this, getAttributes());
}
DecorView其实只是一个帧布局
class DecorView extends FrameLayout mplements RootViewSurfaceTaker, WindowCallbacks {
....
}
这个方法把刚刚创建出来的DecorView传进去
mContentParent = generateLayout(mDecor);
protected ViewGroup generateLayout(DecorView decor) {
....
int layoutResource;
int features = getLocalFeatures();
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;
}
}
.....
//这里传入系统选中的一些布局,并把inflate出来的布局添加进decorViw中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//ID_ANDROID_CONTENT --com.android.internal.R.id.content;
//并通过findViewById查找出R.id.content的布局,作为contentParent返回出去。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
//DecorView中的方法。
oid onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
....
final View root = inflater.inflate(layoutResource, null);
//把系统布局添加到DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) root;
initializeElevation();
}
我们再回到刚开始的地方
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//把刚刚查找出来的R.id.content作为我们自己LayoutResID的根布局添加进去
mLayoutInflater.inflate(layoutResID, mContentParent);
}
我们重新理理 调用phonewindow的setContentView方法–>installDecor()–>generateDecor()创建DecorView–>generateLatout()把系统的布局添加进DecorView中,并查找R.id.content做为我们layout的根布局返回出去–>mLayoutInflater.inflate(layoutResID, mContentParent);
经过这一系列工作之后,我们setContentView,成功的把我们的布局添加进一个id为R.id.content的ViewGroup中,同时这一部分又包含在DecorView中,而DeorView则在PhonWidow对象中。
如下图所示
++图片来源网络++
在AppCompatActivity中setContentView
调用的为AppCompatDelegate的setContentView
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
点进去可以看到原来经行了不同版本的判断
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (BuildCompat.isAtLeastN()) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}
可以看到一个有趣的现象
class AppCompatDelegateImplN extends AppCompatDelegateImplV23
class AppCompatDelegateImplV23 extends AppCompatDelegateImplV14
....
如图所示
++图片取自网络++
所以,我们直接看AppCompatDelegateImplV9的setContentView方法。
public void setContentView(View v) {
public void setContentView(int resId) {
ensureSubDecor();
//找到R.id.content作为我们的布局的根布局
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//把我们的布局添加进contenParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
}
这部分看起来要比Activity清晰很多,我们看下ensureSubDecor()这个方法都做了什么
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
...
}
private ViewGroup 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);
}
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
mWindow.getDecorView();
ViewGroup subDecor = null;
if (!mWindowNoTitle) {
if (mIsFloating) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
mHasActionBar = mOverlayActionBar = false;
}
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
}
if (mOverlayActionMode) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
....
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
contentView.setId(android.R.id.content);
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
// 最后还是调用decorView方法
mWindow.setContentView(subDecor);
return subDecor;
}
经过一些列的判断设值,最终还是调用了PhoneWidow方法的setContentView方法,把视图添加到了DecorVIew中
未完待续…