本文属于安卓页面绘制流程的第2篇,主要介绍Window和DecorView等涉及到页面展示的关键对象是如何创建的,这些主要发生在create Activity的流程中。
开始流程介绍之前,我们先介绍一些比较重要的角色:
1.DecorView:这是最顶层的View,继承自ViewGroup。我们经常设置的setContentView等等,都是放到DecorView中的。
2.ViewRootImpl:最顶层的ViewParent,但是并不是一个View。它属于一个管理者,维护DecorView和Window的关系。绘制流程的控制等等,都是由其来维护的。
3.PhoneWindow:Window翻译过来是窗口,它在安卓中的概念就是锁定屏幕上的一块区域进行显示。而PhoneWindow则是用来装载和显示DecorView的,我们activity中setConentView方法最终也会交给PhoneWindow的setConentView来实现。
4.WindowManagerImpl:WindowManagerGlobal的代理类,基本上功能都是交由WindowManagerGlobal处理。
5.WindowManagerGlobal:视图的管理装载类。一个应用中会有很多activity,其实每个activity都会对应一个DecorView,而这些DecorView都会保存在WindowManagerGlobal中。
6.IWindowSession:WindowManageService在客户端的Binder代理类。客户端最终的绘制操作,需要通过binder传递给WindowManageService,然后由其在传递给SurfaceFlinger去完成最终的合成。
几者的总体关系是这样的:
在Activity的启动流程中,会分别执行ActivityThread的performLaunchActivity和handleResumeActivity的方法。
其中本文要介绍的Window和DecoreView的创建都发生在performLaunchActivity方法中,所以,我们来看一下这个方法:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
//1.Activity的创建
Activity activity = null;
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
//2.Activity的关联
activity.attach();
//3.执行Activity的onCreate流程
mInstrumentation.callActivityOnCreate()
...
}
首先,会通过反射生成Activity对象;
然后,会执行Activity的attach流程,会把对象关联到系统侧,并且创建Window对象,Window就是页面的容器。这一块,我们第二章来讲。
最后,绘制行Activity的onCreate方法,这时候,我们往往会执行setContentView方法,这时候,就会创建DecorView,ContentParent等元素。
各个元素的结构如下图所示,其中ContentParent就是所有View的父容器,我们调用setContentView()方法,也是往ContentParent中添加View。
在Activity的启动流程中,会分别执行ActivityThread的performLaunchActivity和handleResumeActivity的方法。本文主要讲解的是performLaunchActivity方法中的几个关键点。
我们首先看一下ActivityThread.performLaunchActivity中的代码:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
//Activity的创建
Activity activity = null;
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
//Activity的关联
activity.attach();
//执行Activity的onCreate流程
mInstrumentation.callActivityOnCreate()
...
}
我们看一下activity.attach中实现的相关内容:
//android.app.Activity
final void attach(){
//1
mWindow = new PhoneWindow();
//3
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),...);
mWindow.setColorMode(info.colorMode);
}
//com.android.internal.policy.PhoneWindow.java
public PhoneWindow(@UiContext Context context) {
public PhoneWindow(@UiContext Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
}
主要执行了以下的逻辑:
1.创建了Activity所绑定的Window,成员名为mWindow,类型为PhoneWindow。
2.在PhoneWindow中,mLayoutInflater赋值。我们的布局就是通过mLayoutInflater对象去解析的。
3.给Window对象绑定WindowManager,这个WindowManager实际上是WindowManagerImpl。
所以此时,Activity中的mWindow,以及PhoneWindow中的mWindowManager和mLayoutInflater都已经有值了。
接下来,我们看下callActivityOnCreate的流程。Activity.onCreate流程中并没有和window相关的逻辑,但是正常流程,我们都会在onCreate中调用setContentView设置布局,答案就是在这个方法中,我们一起看一下:
//android.app.Activity.java
public void setContentView(@LayoutRes int layoutResID) {
//1
getWindow().setContentView(layoutResID);
}
//com.android.internal.policy.PhoneWindow
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//2
installDecor();
}
...
//3
mLayoutInflater.inflate(layoutResID, mContentParent);
}
private void installDecor() {
if(mDecor == null){
mDecor = generateDecor(-1);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
主要执行了以下的逻辑:
1.调用Activity的setContentView方法,其实就是调用其所持有window的setContentView方法。而这个window对象自然是上面流程中创建的PhoneWindow。
2.首次调用的时候,通过installDecor方法去创建根布局DecorView。而mContentParent其实是mDecor中的一个子View。
3.通过mLayoutInflater对象去解析生成布局对象,并且关联到父容器mContentParent上。我们具体讲一下这里的generateLayout方法。
我们看一下generateLayout这个方法:
//PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
...
int layoutResource;
if(...){
layoutResource = R.layout.screen_title_icons;
} else if(){
layoutResource = R.layout.screen_custom_title;
} else {
layoutResource = R.layout.screen_simple;
}
...
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
//Window.java
public T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
//DecorView.java
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
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 {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
我们做一下总结:
首先,在attch中,创建了Window类型的对象PhoneWindow,
然后,onCreate中我们一般会调用setContentView方法,这时候会生成加载xml生成DecorView,而contentParent就是DecorView中的View。
整个流程如下图所示:
至此,window中的元素都已经准备好了,但是这时候只是APP侧准备好了,还没有和系统建立通讯。所以,接下来要做的就是把这个Window向系统侧进行注册,这一块就是我们下一章要将的内容了。
1.如果onCreate中不调用setContentView,那么会执行后面的流程吗?
答:会的,即使不调用setContentView,还是会创建DecorView,而DecorView中仍然会有ContentView的父容器。只不过此时的父容器没有任何子View,显示为一片空白而已。
2.如果我在ActivityB中,想拿到上一个页面ActivityA的View该怎么做呢?
答:取WindowManagerGlobal中的mViews。