我们在Activity中的onCreate方法中通过setContentView(R.layout.activity_main)就把我们的xml布局显示出来,那么Android系统具体帮我们做了那些事情呢,跟进源码
public class UIActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
我们发现setContentView是在getWindow类里去执行,但我们点进去发现getWindow是一个Window抽象类,那么我们看一下Window是什么
/**
* 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.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {}
通过注释我们了解到Window是一个承载View的区域,它的唯一实现类是PhoneWindow ,那么我们去该类中寻找setContentView方法
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//这里我们看到有个方法,跟进去
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//创建DecorView
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);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
}
...
}
跟进去我们看到有两个判空if (mDecor == null)和if (mContentParent == null),我们看一下这两个对象代表什么
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
通过注释我们了解到DecorView是Window(PhoneWindow)的第一级布局,mContentParent是一个ViewGroup,它可以是DecorView也可以是DecorView的子View,那么我们继续看源码
mDecor = generateDecor(-1);
这里我们跟进去
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();
}
//可以看到这里是new出来decorView
return new DecorView(context, featureId, this, getAttributes());
}
再来回来看创建mContentParent
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
}
protected ViewGroup generateLayout(DecorView decor) {
...省略,我们只看关键代码...
//这里发现开始inflate Decorview
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
...
else {
// Embedded, so no decoration is needed.
//这里的simple就是系统生成的布局,内部是线性布局
//可以查看系统SDk下面的布局文件找到
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
//这里面把我们自己的Xml放到系统布局的framlayout中
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
。。。
//最终返回出来我们自己的xml
return contentParent;
}
可以看到这里根据不同的样式对应不同的布局layoutResource 我们看一下系统自己的XML布局screen_simple
我们发现这个布局是纵向的线性布局上面是个id为action_mode_bar_stub的ViewStub,下面是个id为content的FrameLayout,然后通过mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);加载进DecorView中,我们跟进去看一下
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
//看到把刚才的一些布局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));
}
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();
}
可以看到mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)最后addView到我们DecorView中,我们再回到PhoneWindow往下看代码
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
我们先按住Ctrl然后数遍移动到这个id上发现id号为16908290 这里通过findViewById生成ViewGroup,这个id我们点进去
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
我们查看下id
发现这个id就是我们上面看到的xml中的FrameLayout布局,然后return contentParent;把该布局返回出去。现在我们就了解到上文的mContentParent就是系统xml中的FrameLayout布局,到这里我们的installDecor方法跟进完了。继续回过头回到setContentView(int layoutResID)方法,往下看关键代码
//这里判断是否有反转动画
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//正常布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
发现这里先判断是否有转场动画,这里的layoutResID就是我们自己的xml布局,layoutResID就是DecorView中的FrameLayout布局我们先看没有转场动画的,跟进去
**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();
}
}
到这发现开始解析我们的xml文件,转场效果同理通过scene.enter();最终也进入inflate流程
总结一下:Activity中的setContentView是调用PhoneWindow中的setContentView。该方法中在installDecor,通过generateDecor生成DecorView,通过generateLayout,判断不同的样式添加进DecorView中,然后将系统的FrameLayout布局返回到mContentParent当中,然后把我们的自己的xml布局添加进该FrameLayout中
但是我们的Activity是继承Activity我们发现Android5.0之后系统默认继承AppCompatActivity,那么我们再来分析系统做了哪些改动
public class UIActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
跟进去
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
发现我们进入到了AppCompataActivity类中,发现该类也是抽象类,在该类的上面我们找到该类的实现类AppCompatDelegateImpl,我们跟进去找到setContentView方法
public void setContentView(int resId) {
//跟进这个方法
this.ensureSubDecor();
//发现这个id跟content的id一样都是16908290
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
我们先看ensureSubDecor()
private void ensureSubDecor() {
if (!this.mSubDecorInstalled) {
//发现这里去创建一个subDecor
this.mSubDecor = this.createSubDecor();
。。。
}
}
private ViewGroup createSubDecor() {
...
//往下寻找关键代码
...
if (this.mOverlayActionMode) {
subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
} else {
//这里的subDecor是一个ViewGroup
subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
}
...
ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
if (windowContentView != null) {
while(windowContentView.getChildCount() > 0) {
View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
//这里把原来xml中的content的id设置为-1,把subDecorView里面的framlayout设置为原来的id
windowContentView.setId(-1);
contentView.setId(16908290);
if (windowContentView instanceof FrameLayout) {
((FrameLayout)windowContentView).setForeground((Drawable)null);
}
}
//再把整个SubDecorView添加进Framlayout中
this.mWindow.setContentView(subDecor);
}
我们看到跟原来的方式差不多不过这个布局换成了abc...我们还看abc_screen_simple布局,该布局是在v7包下面
FitWindowsLinearLayout,ViewStubCompat,ContentFrameLayout,这里是Android5.0谷歌做了适配,弥补原来的适配问题
我们接着分析
ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
这里拿到我们看到的ContentFrameLayout,它的id名称为action_bar_activity_content,然后又通过ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);拿到上面我们看到的熟悉的16908290的FrameLayout,然后遍历原来的Framelayout的子View,把它的子View添加到新的ContentFrameLayout中,然后把windowContentView.setId(-1);原来的Framelayout的id设为-1,把新的ContentFrameLayout的id设为16908290,最终通过this.mWindow.setContentView(subDecor);把新的subDecor添加到原来FrameLayout的位置
如图