这是一篇android基础文章,算是复习和巩固一下,追了一下最新Android版本的代码,看看和6年前有什么变化。直接按顺序来了,不想凑字数,反正也没啥人看,就当写笔记了。
有点基础的人都知道,Activity内部先是一个Window,Window内部才是View。开整…
创建一个正常的Activity,我们都知道有3个步骤,1继承Activity;2实现onCreate()方法,内部调用setContentView(xml布局id);3在清单文件中注册。没有感觉到有Window和View的事,不卖关子直接来。
Activity内部有一个mWindow变量,在attach()方法中被赋值,这里发现mWindow是一个PhoneWindow,PhoneWindow是Window抽象类的唯一实现类。也就是说每个Activity都有一个自己的phoneWindow。
public class Activity {
private Window mWindow;
final void attach(....) {
mWindow = new PhoneWindow(this, window, activityConfigCallback);
}
}
attach()方法先于onCreate()方法被调用,so onCreate被调用时候Window对象已经创建好了,接下来就到了Activity的setContentView()方法。
//Activity类中
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}
调用了window的setContentView(),也就是PhoneWindow的setContentView()。
//PhoneWindow类中
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor(); // #1
}
mLayoutInflater.inflate(layoutResID, mContentParent); // #2
}
这个方法就已经包括了布局加入到Activity中的全部流程了。
我们先看 #2这行 我们传入的布局id, 最终通过LayoutInflater 加入到了一个叫做mContentParent的ViewGroup对象。
那么mContentParent对象又是啥?在哪里创建的?答案就在 #1
//PhoneWindow类中
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1); // #3
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor); // #4
}
}
installDecor方法中,完成对mContentParent的赋值(#4),赋值需要一个mDecor变量,发现它的赋值逻辑就在上面代码(#3),而且发现mDecor是一个叫DecorView类型的引用。并且源码中的注释很有料
//PhoneWindow类中
// This is the top-level view of the window, containing the window decor.
//这是Window的顶层View
private DecorView mDecor;
哈,我们找的了Activity中的根view。并且发现它继承自FrameLayout。
哦哦,那也就是说,我们setContentView传入的布局应该会被放入这个DecorView中啊。没错,但接着思考,发现之前(#2) 明明是布局被放入到mContentParent这个ViewGroup中的啊。
我们带着疑问,追一下( #4) mContentParent = generateLayout(mDecor);
//PhoneWindow类中
protected ViewGroup generateLayout(DecorView decor) {
//第一步 拿到一个系统提供的xml布局id
int layoutResource;
if(xxx){
layoutResource = R.layout.系统提供的布局id
} else if(xxx){
layoutResource = R.layout.系统提供的布局id
}else if(xxx){
layoutResource = R.layout.系统提供的布局id
}
//第二步
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//第三步
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
这个方法里面
第一步先根据条件(设置的主题等,比如有无actionBar? 是否全屏?) 拿到一个系统提供的xml布局id。这些系统布局都有一个共同之处,就是里面都放入一个id为content的FrameLayout。
第二步把 id传给DecorView中的onResourcesLoaded方法
//DecorView类中
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
final View root = inflater.inflate(layoutResource, null);
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
哦哦,是把第一步的系统布局对象,直接加入到DecorView中。并且充满DecorView,放在最底层。
第三步,把系统布局文件中id为content的FrameLayout返回。
我们回过头来再看,( #4) mContentParent = generateLayout(mDecor); 总结它干的事情就是把DecorView中放入了一个系统的布局文件,在把布局文件中的一个FrameLayout 赋值给 mContentParent。
哦哦哦,刚才的疑问解开了,我们自己setContentView传入的布局确实会被放入到DecorView中。但不是直接放,是放入了DecorView中的一个系统提供好的布局中的一个id名为content的FrameLayout。 这也就是方法取名为set"Content"View的原因了。
最后,我们的xml文件就放入到Activity中了。一图胜千言