最近两天,看了很多关于android中view加载的过程。在网上看到很多大神的讲解,也深深的被透明透彻的讲解所折服,同时也非常感谢他们的无私奉献。 再次,我非常感谢如下的大神的博文:
郭霖:http://blog.csdn.net/guolin_blog/article/details/12921889
qinjuning:http://blog.csdn.net/qinjuning/article/details/7226787
本文也是根据他们的讲解,加上我个人的理解,更加细化的对view的加载过程给予说明。
首先,既然我们的研究对象的setContentView()方法,那么我们就查看该方法的具体实现:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
再以上的语句是很简单的,但是内部做了很多的复杂的处理,这个处理,主要是交给了getWindow()返回的window类。但是如果你看到源码的化,你就会知道,实际上Window类是一个抽象类,代码如下:
public abstract class Window {
...
public abstract void setContentView(int layoutResID);
...
}
一个抽象类的抽象方法,我们是无法直接调用的,这就说明,在android的框架内,必然是有一个window的实现类的,没错!这个实现类就是PhoneWindow。 我怎么就知道是PhoneWindow类而不是其他的呢?好吧!我们继续看代码,在Activity.java中有一个 Internal API 方法attach();我们只需要知道一句代码就好了。
mWindow = PolicyManager.makeNewWindow(this);
我们顺着这条线,继续往下,查看PolicyManager.makeNewWindow()方法:
public PhoneWindow makeNewWindow(Context context) {
return new PhoneWindow(context);
}
至此我们就可以得出,我们在Activity中使用setContentView()方法,实际上是交给一个叫做PhoneWindow的类来处理的。下面我们将着重去了解PhoneWindow的API。
注:如果没有下载android的源码的话,这个类你是看不到的。你如果需要查看的话,可以从此查看:
PhoneWindow.java
在此有必要提一下什么是PhoneWindow?
PhoneWindow:代表的就是你的当前屏幕所有的内容;
----|private DecorView mDecor:该类是一个PhoneWindow的内部类,实现于FrameLayout,代表了 PhoneWindow作用域当中的根布局。
----|private ViewGroup mContentParent:该成员变量就是我们的布局存放的位置。也就是说我们调用setContentView()当中传进去的布局就放置在该位置。其在布局中的id = content
对于以上的两个成员变量,如果不明白,暂且放置,一会我会做些许的说明。
在此,就只贴setContentView的代码即可,如需查看其他,点击上面的连接即可。
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}
现在,我们开始分析PhoneWindow中的setContentView()方法。
首先会判断mContentParent是否为空,关于mContentParent的这个成员变量,我之前已经说过,它就是代表我们的布局要存放位置,所以是一个ViewGroup。当第一调用时,自然是为null的,此时会调用installDecor()方法。
下面分析:installDecor()
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
}
}
该方法的实际目的就是初始化mDecor和mContentParent。
现在分步分析初始化二者的代码;
首先会进行mDecor方法:会调用generateDecor()
该方法很简单,就是一个简单的创建对象的过程:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
这句没有什么好说的,关键是对mContentParent的初始化,该初始化过程会调用generateLayout() 该方法的内容很多,就不贴全部了,如果想要查看,可以点击我上述给的链接。
实际上对mContentParent的初始化主要是根据你的窗体的主题内容进行初始化,所以当你选择不同的theme 时,你的窗体的样式就会不一样,这里的不一样就是该步所生成的,因为该方法的第一行就是获取一个窗体的样式。
TypedArray a = getWindowStyle();
而之后就是根据这些样式,进行窗体的初始化。
我就从填充我们的布局那部分开始吧:
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
Drawable drawable = mBackgroundDrawable;
if (mBackgroundResource != 0) {
drawable = getContext().getResources().getDrawable(mBackgroundResource);
}
mDecor.setWindowBackground(drawable);
drawable = null;
if (mFrameResource != 0) {
drawable = getContext().getResources().getDrawable(mFrameResource);
}
mDecor.setWindowFrame(drawable);
// System.out.println("Text=" + Integer.toHexString(mTextColor) +
// " Sel=" + Integer.toHexString(mTextSelectedColor) +
// " Title=" + Integer.toHexString(mTitleColor));
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
if (mTitle != null) {
setTitle(mTitle);
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
在以上代码中,请注意这一句:
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
明白了吗?这个函数的作用总结一下,就是根据在xml设置的样式,或者 在调用setContentView()之前,对窗体设置了一些参数的初始化过程,然后返回一个布局的持有对象,即我们要放置我们的布局的那个布局。有点绕。请细细体会。
下面我们再重新回到那个方法:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}
再次看到这个方法时,是不是就不那么陌生了呢?你会发现,在判断mContentParent时,会有else的分支,当它不会null的话,也就是说我之前已经调用过该方法,再次调用时,就不用在进行installDecor()方法了,因为我已经进行了初始化,现在只需要把我的布局加载到上面就可以了,要记住,我们的布局加载过程,并不是在installDecor()中进行的,而是在setContentView方法内部,而installDecor方法的目的就只是根据我设置的参数来初始化该Activity的窗体的样式,和返回一个放置我布局的一个布局的持有对象,只有获得了后者,我才可以将我的布局加载到activity中。
mLayoutInflater.inflate(layoutResID, mContentParent);这个方法,相信你一定明白,就是将我的布局,加载到父View mContentParent中。然后调用cb.onContentChanged();来更新activity的布局。 至此,完成以上步骤仅仅只是把你的布局绑定在当前的activity,要想显示到屏幕上还需要其他的步骤。