从入门应用Hello World 说起,我们简单的用setContentView设置了一个布局,然后启动我们的应用,手机界面就显示出Hello World的布局,这个背后到底发生了什么,这就是我今天所研究的对象。
1.概述
首先弄清楚几个跟View有关的概念
-
窗口(Window):这是一个纯语义的说法,即程序员所看到的屏幕上的某个独立的界面,比如一个带有Title Bar 的Activity 界面、一个对话框、一个Menu 菜单等,这些都称之为窗口。这里所说的窗口管理一般也都泛指所有这些窗口,在Android 的英文相关文章中则直接使用Window 这个单词。而从WmS 的角度来讲,窗口是接收用户消息的最小单元,WmS 内部用特定的类表示一个窗口,以实现对窗口的管理。WmS 接收到用户消息后,首先要判断这个消息属于哪个窗口,然后通过IPC 调用把这个消息传递给客户端的ViewRoot 类。
-
Window 类:该类在android.view 包中,是一个abstract 类,该类是对包含有可视界面的窗口的一种包装。所谓的可视界面就是指各种View 或者ViewGroup,一般可以通过res/layout 目录下的xml 文件描述。
-
ViewRoot 类:该类在android.view 包中,客户端申请创建窗口时需要一个客户端代理,用以和WmS 进行交互,这个就是ViewRoot 的功能,每个客户端的窗口都会对应一个ViewRoot 类。
-
W 类:该类是ViewRoot 类的一个内部类,继承于Binder,用于向WmS 提供一个IPC 接口,从而让WmS 控制窗口客户端的行为。
描述一个窗口之所以使用这么多类的原因在于,窗口的概念存在于客户端和服务端(WmS)之中,客户端所理解的窗口和服务端理解的窗口是不同的,因此,在客户端和服务端会用不同的类来描述窗口。同时,无论是在客户端还是服务端,对窗口都有不同层面的抽象,比如在客户端,用户能看到的窗口一般是View 或者ViewGroup 组成的窗口,而与Activity 对应的窗口却是一个DecorView 类,而具备常规Phone 操作接口的窗口却又是一个PhoneWindow 类。
关系图盗用老罗的图片描述下
至于一个手机窗口的具体组成,网上也有图描述的很清楚了
一般来说DecoView包含一个LinearLayout,(这里的一般指的是R.Layout.screen_title后续会有介绍)这个LinearLayout又包含标题和内容区域两个FramLayout,用SDK中tools文件夹下hierarchyviewer bat 查看ViewTree可以证实这一点
同样可以在代码中证实这一点
获取ContentView: findViewById(android.R.id.content)
获取ContentView上一级:ContentView.getParent
获取DecorView:上面的对象再getParent 或者直接 getWindow().getDecorView()
理论说了这么多,talking is cheap, show me the code!
2.setContentView源码解析
Acvitivity.setContentView
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*/
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
}
public Window getWindow() {
return mWindow;
}
getWindow()到底是个什么鬼,打印一下发现是 com.android.internal.policy.impl.PhoneWindow
接下来看 PhoneWindow.setContentView
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor(); // 1、生成DecorView和mContentParent
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);// 2、将layoutResId的布局添加到mContentParent中
final Callback cb = getCallback();<span style="font-family: Arial, Helvetica, sans-serif;">// 3、窗口回调函数</span>
if (cb != null) {
cb.onContentChanged();
}
}
我们可以看到,setContentView的基本流程简单概括就是如下几步:
1、构建mDecor对象和mContentParent。mDecor就是整个窗口的顶层视图,它一般包含了Title和ContentView两个区域 ,Title区域就是我们的标题栏,ContentView区域就是显示我们xml布局内容中的区域。
2、设置一些关于窗口的属性,初始化标题栏区域和内容显示区域;
3、调用Window的回调方法。
下面详细解读上面三个部分
2.1 构建mDecor对象和mContentParent
重点分析PhoneWindow.installDecor函数
// 构建mDecor对象,并且初始化标题栏和Content Parent(我们要显示的内容区域)
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(); // 1、构建DecorView
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor); // 2、获取ContentView容器,即显示内容的区域
mTitleView = (TextView)findViewById(com.android.internal.R.id.title); //3、设置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);
}
}
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1); // 构建mDecor对象
}
接着看PhoneWindow.generateLayout,这个函数有点长,也是本文分析的重点,稍微精简一下
// 返回用于显示我们设置的页面内容的ViewGroup容器
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
// 1、获取窗口的Style属性
TypedArray a = getWindowStyle();
if (false) {
System.out.println("From style:");
String s = "Attrs:";
for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {
s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="
+ a.getString(i);
}
System.out.println(s);
}
// 窗口是否是浮动的
mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
// 设置是否不显示title区域
if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);