setContentView()
参考Android 8.0源码
- Window
每一个Activity都持有一个Window对象,Window是一个抽象类,提供了绘制窗口的一组通用API。 - PhoneWindow
Android为Window提供了唯一的实现类PhoneWindow,也就是说Activity中Window实例是一个PhoneWindow对象,我们通过PhoneWindow具体去绘制窗口。但是PhoneWindow终究是Window,不具备多少View相关的能力。不过PhoneWindow中持有一个Android中非常重要的一个View对象DecorView。DecorView继承FrameLayout,是所有应用窗口(Activity界面)的根View。简而言之,PhoneWindow类是把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作接口。PhoneWindow是Android中最基本的窗口系统,每个Activity均会创建一个PhoneWindow对象,是Activity和整个View系统交互的接口。
也就是说,每一个Activity持有一个PhoneWindow的对象,而一个PhoneWindow对象持有一个DecorView的实例,Activity中View相关的操作其实大都是通过DecorView来完成的。 - DecorView
DecorView继承FrameLayout,对普通的FrameLayout进行功能的扩展,是所有应用窗口(Activity界面)的根View。本来是PhoneWindow的内部类,在我所看的Android 8.0版本源码中,从PhoneWindow类中抽取了出来。主要有以下功能:
1 . Dispatch ViewRoot分发来的key、touch、trackball等外部事件;
2 . DecorView有一个直接的子View,我们称之为System Layout,这个View是从系统的Layout.xml中解析出的,它包含当前UI的风格,如是否带title、是否带process bar等,可以称这些属性为Window decorations;
3 . 作为PhoneWindow与ViewRoot之间的桥梁,ViewRoot通过DecorView设置窗口属性。可以通过View view = getWindow().getDecorView();获取。
4 . DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏、标题栏、内容显示栏这三块区域。DecorView里面TitleView表示标题,可以设置requestWindowFeature(Window.FEATURE_NO_TITLE)取消。ContentView表示内容栏,是一个id为content的FrameLayout。我们平常在Activity中使用setContentView()就是设置这个的。
跟踪setContentView()方法的
当系统(一般是ActivityManagerService)配置好启动一个Activity的相关参数(包含Activity对象和Window对象信息)后,就回回调Activity的onCreate()方法,在其中我们通过设置setContentView()方法类设置该Activity的显示界面。我们通过在Activity中设置setContentView()方法,将写好的布局文件展示在Activity的内容显示区域中。其实就是通过不断地传递,把布局文件对应的资源id一只传递到这个Activity对应的DecorView中,DecorView继承自FrameLayout,当DecorView接收到来自Activity传递过来的布局id后,通过inflater()方法把布局资源id转换为一个View,然后把这个布局View添加在自身中,我们就可以在Activity中看到指定的布局了。
我们从Activity.java类中的setContentView()开始分析。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()方法返回的是Window对象,但是Window是一个抽象类,我们使用的是它的实现类PhoneWindow。在PhoneWindow.java中,setContentView()方法如下:
@Override
public void setContentView(int layoutResID) {
//mContentParent是我们的Activity显示的界面;
//如果mContentParent为null,就生成DecorView,然后加载生成;
//如果mContentParent不为null,就说明已经存在DecorView了,就直接加载要设置的布局文件到DecorView作为子View;
if (mContentParent == null) {
//生成DecorView;
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 {
//将layoutResID的布局添加到mContentParent中;
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
//构建DecorView,并且初始化标题栏和mContentParent(我们那要显示的内容区域);
private void installDecor() {
if(mDecor == null){
//构建DecorView;
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if(mContentParent == null){
//构建显示内容的区域;
mContentParent = generateLayout(mDecor);
//设置Title等;
//代码省略;
}
}
在setContentView()中基本流程如下:
1 . 构建mDecor对象,mDecor就是整个窗口的顶层视图,它主要包含了Title和Content View两个区域,Title区域是我们的标题栏,Content View区域是显示xml布局内容中的区域。
2 . 设置一些关于窗口的属性,初始化标题栏区域和内容显示区域。
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
//获取DecorView;
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
//获取窗口的Style属性;
TypedArray a = getWindowStyle();
//设置一些窗口属性等,代码省略;
//布局资源id;
int layoutResoure;
int features = getLocalFeatures();
//代码省略;
//R.layout.xxx有好几种布局,这里只举了一种布局;
layoutResource = R.layout.screen_simple;
//代码省略;
mDecor.startChanging();
//加载布局资源到DecorView中;
mDecor.onResourcesLoaded(mLayoutInflater,layoutResource);
//获取到显示内容的内容区域,其实就是FrameLayout,ID_ANDROID_CONTENT就是com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//代码省略;
//返回内容显示区域;
return contentParent;
}
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
//代码省略;
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
在installDecor()方法中,会加载布局到DecorView中。
在DecorView中,有两个区域,title(系统标题栏)区域和content(我们设置的layout布局显示)区域。
其中在generateLayout()方法中,有好几种系统内置的layout布局文件,会根据用户设置的一些属性来选择使用哪种布局,比如FEATURE_NO_TITLE时就选择没有title的布局文件。generateLayout()方法只是获取content区域的视图,其实就是一个FrameLayout。我们使用setContentView()方法,这个方法中的参数值代表的布局就是放在content里面的。
也就是说安卓系统预先设置了一些布局资源,这些布局资源里面有一个留给开发者设置窗口内容的区域,也就是content区域,我们通过setContentView()设置的布局会被添加到content布局中,这个content布局是一个FrameLayout。
当Activity启东市,通过onCreate()方法让用户设置自己的界面,系统将这个布局界面添加到一个内置的布局界面的content区域中,此时,DecorView就建立起来了。然后调用onStart()方法,并且在调用onResume()方法之前将DecorView添加到WindowManager中,并且设置Activity为可见状态,然后通知ActivityManagerService,该Activity已经变为resume状态了,使系统能够渲染Activity的视图,到这里,Activity视图就显示在手机等设备上面了。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--------------------last line for now--------------------