@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
写安卓的小伙伴一定很熟悉这段代码,我们每次创建一个活动都会有这么一段代码,但是setContentView(R.layout.activity_main)这么简简单单的一段代码做了事情可不简单,接下来我们会跟着源码大概走一遍这个方法。
点击进入方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}
看的出来是得到window后调用window的方法,而window只有一个实现类就是PhoneWindow,接下来我们看其重写的方法(注意:一个Activity对应一个Window)
@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;
}
其中有6处我觉得可能重要的点,我会一个个看,但是不保证看懂,看不懂就不讲了,有大佬知道可以评论告诉我
installDecor()
private void installDecor() {
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);
//省略超多源码
一开始mDecor和mContentParent都是不存在的所以
重点在于mDecor = generateDecor(-1)和mContentParent = generateLayout(mDecor)
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, this);
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
就是返回一个new DecorView
这里介绍一下DecorView
DecorView继承于FrameLayout,是我们界面中最顶层的View
DecorView继承于FrameLayout,然后它有一个子view即LinearLayout,方向为竖直方向,其内有两个FrameLayout,上面的FrameLayout即为TitleBar之类的,下面的FrameLayout即为我们的ContentView,所谓的setContentView就是往这个FrameLayout里面添加我们的布局View
接下来是generateLayout(mDecor)
protected ViewGroup generateLayout(DecorView decor) {
//...
//省略一些超级长,设置Window样式的代码,直接来看我们最关心的代码!
ViewGroup contentParent =(ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...
return contentParent;
}
}
饶来绕去,就是在mDecor中查找ID_ANDROID_CONTENT的对应的View,然后是赋值给contentParent,在generateLayout方法中返回contentParent,最终赋值给mContentParent,换句话说,mContentParent是mDecor的子view,而我们自己布局对应的view,是mContentParent的子view
接下来我们继续看setcontentview后面的源码
if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
//可疑点二
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
//可疑点三
}
这里根据hasFeature(FEATURE_CONTENT_TRANSITIONS)的返回值选择该调用哪个方法,(没找到哪里设置的,网上查阅默认是true,看代码一个是移除allview,一个是传入scene猜测也应该是第二个)
public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) {
SparseArray scenes = (SparseArray) sceneRoot.getTag(
com.android.internal.R.id.scene_layoutid_cache);
if (scenes == null) {
scenes = new SparseArray();
sceneRoot.setTagInternal(com.android.internal.R.id.scene_layoutid_cache, scenes);
}
Scene scene = scenes.get(layoutId);
if (scene != null) {
return scene;
} else {
scene = new Scene(sceneRoot, layoutId, context);
scenes.put(layoutId, scene);
return scene;
}
}
创建一个scenes 然后将context,和layoutId的信息装进去
private void transitionTo(Scene scene) {
if (mContentScene == null) {
scene.enter();
} else {
mTransitionManager.transitionTo(scene);
}
mContentScene = scene;
//点击第二个
public void transitionTo(Scene scene) {
// Auto transition if there is no transition declared for the Scene, but there is
// a root or parent view
changeScene(scene, getTransition(scene));
}
private static void changeScene(Scene scene, Transition transition) {
final ViewGroup sceneRoot = scene.getSceneRoot();
if (!sPendingTransitions.contains(sceneRoot)) {
Scene oldScene = Scene.getCurrentScene(sceneRoot);
if (transition == null) {
// Notify old scene that it is being exited
if (oldScene != null) {
oldScene.exit();
}
scene.enter();
} else {
sPendingTransitions.add(sceneRoot);
Transition transitionClone = transition.clone();
transitionClone.setSceneRoot(sceneRoot);
if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
transitionClone.setCanRemoveViews(true);
}
sceneChangeSetup(sceneRoot, transitionClone);
scene.enter();
sceneChangeRunTransition(sceneRoot, transitionClone);
}
}
}
可以发现最终都进入scene.enter()方法
public void enter() {
// Apply layout change, if any
if (mLayoutId > 0 || mLayout != null) {
// empty out parent container before adding to it
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
// Notify next scene that it is entering. Subclasses may override to configure scene.
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
最终调用LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot)
将我们自己的view放在了刚刚创建的mContentParent中
如前文布局简图所示
最后在setContentView中调用
mContentParent.requestApplyInsets();
这个函数就是将我们设置好的布局插入到屏幕中
总结(第3步和第4步源码在generateLayout(DecorView decor)中,是大量判断语句有兴趣的同学请自行阅读)
- 在Activity中调用setContentView(实际调用PhoneWindow#setContentView)
- 新建DecorView实例
- 设置界面主题(requestFeature)
- 确定主题界面(layoutResource = R.layout.xxx)
- 在主题界面抽取内容ViewGroup(mContentParent = findViewById)
- 将我们自己创建的布局界面和Android提供的内容mContentParent打包进Scene
- 通过LayoutInflater解析布局,将布局转化为View
- 将view添加到mContentParent中
- 将整个界面装载到系统界面中