关键类:window,PhoneWindow,DecorView
关键方法:setContentView(int layoutResID)及其重载方法
简单介绍:
- Window是一个抽象类,提供了绘制窗口的一组通用API
- PhoneWindow是Window的具体继承实现类。
- DecorView原为PhoneWindow的内部类,后独立出来。DecorView是所有窗口的根View (FrameLayout的子类)
关系图:
源码解读:
Step1:Activity类
setContentView()是非常关键的方法,其重载方法有3个
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
可以清楚的看到3个重载方法都调用了getWindow()中相应的setContentView方法
Step2:PhoneWindow类
由于Window类为抽象类,所以我们要看实现类PhoneWindow里的setContent(int layoutResID)
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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
- 判断mCntentParent是否null,
- 为空:调用installDecor(),初始化DecorView
- 不为空:判断是否设置FEATURE_CONTENT_TRANSITIONS(默认false)
- 若没设置:清除mContentParent的子视图
- mLayoutInflater.inflate(layoutResID, mContentParent)将资源文件转换为View并加载至mContentParent
Step3:PhoneWindow类
- setContentView(int layoutResID)
- setContentView(View view)
- setContentView(View view, ViewGroup.LayoutParams params)`
后两个方法与第一个方法大同小异
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
setContentView(View view)实际上是调用了setContentView(View view, ViewGroup.LayoutParams params)只是LayoutParams设置为了MATCH_PARENT而已。
setContentView(View view, ViewGroup.LayoutParams params)方法调用addView()加载视图至mContentParent
Step4:PhoneWindow类
到这里,大家都知道我们所加载的布局最终都是add到mContentParent,那mContentParent从哪里来呢?我们回头看一下installDecor()方法
ViewGroup mContentParent;//内容视图
private DecorView mDecor;//所有视图的根视图
//实例化DecorView
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent
mContentParent = generateLayout(mDecor);
//......
//初始化一堆属性值
}
}
- 判断mDecor是否为null,为空调用generateDecor()创建一个DecorView
- 判断mContentParent是否为null,为空调用generateLayout(mDecor)创建一个mContentParent对象
此时我们看到了前几步所需的mContentParent
Step5:PhoneWindow类
接着我们看mContentParent是具体如何创建的
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//......
//依据主题style设置一堆值进行设置
// Inflate the window decor.
int layoutResource;
//要在setContentView之前调用requesetFeature的原因
int features = getLocalFeatures();//获取本地特性
//......
//根据设定好的features值选择不同的窗口修饰布局文件,得到layoutResource值
//把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//......
//继续一堆属性设置,完事返回contentParent
return contentParent;
}
- 根据app所设置的不用类型主题获取相对应的系统根布局文件(这些布局都有一个以id为content的帧布局:内容布局)
- 解析这些布局文件并加载至mDecor视图
- 接着通过mDecor获取id为content的帧布局返回给contentParent对象(前几步的关注对象)
主题的话以后有空的话专门写一篇文章介绍,这里大概认识一下~
当我们使用(Window.FEATURE_NO_TITLE)主题,则它对应的系统布局为R.layout.screen_simple
有没有看到R.id.content的FrameLayout么?这就是上面讲的系统布局内的内容视图
Step6:结束 Finish
此时我们的布局文件文件已经转换为视图并加载到DecorView当中,但是此时布局并没有显示!那视图是如何显示出来呢?接下去的文章会分析这一块。
总结 Summary
通过以上6步我们大致可以理解setContentView的整个过程:
- 创建DecorView的对象,并将此对象作为窗口的根视图
- 根据主题及其样式的不同获取对应的布局文件,解析并加载至mDecorView
- 通过mDecorView获取id为content的帧布局contentParent,
- 将Activity的布局文件解析并加载至contentParent
理解图:
额外 Extra
Step1 PhoneWindow类
这里我们聊一下与setContent有关的回调,我们回看一下刚才的setContentView
public void setContentView(int layoutResID) {
......
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
当我们setContentView执行完加载视图后,接着获取回调CallBack,并且执行改回调的方法onContentChanged()。此回调从哪里来呢?
Step2 Window类
private Callback mCallback;
public final Callback getCallback() {
return mCallback;
}
public void setCallback(Callback callback) {
mCallback = callback;
}
public interface Callback{
......
public void onContentChanged();
......
}
从这里我们可以清楚的看到Window类中包含了Callback和所对应的get( )和set( ),
getCallback()我们已经知道在哪里调用,那setCallBack呢?
Step3 Activity类
//此类为简化版
public class Activity implements Window.Callback {
final void attach(Context context, ActivityThread aThread){
......
mWindow.setCallback(this);
......
}
//接口所调用的方法
public void onContentChanged() {}
}
我们可以看出Activity实现了Callback的接口,且设置setCallback为this。所以setContent()执行完毕后就会调用此方法。setContent()竟然是空方法
疑问 Wonder
此文只讲到DecorView由PhoneWindow生成并加载的。我们知道phoneWindow是从Activity的getWindow()获取的。那PhoneWindow跟Activity是如何产生关联的呢?
Android视图加载流程(2)之Window和WindowManager的创建与Activity
PS:本文
整理
自以下文章,若有发现问题请致邮[email protected]
工匠若水 Android应用setContentView与LayoutInflater加载解析机制源码分析
Hohohong Android窗口机制