我们通常在Activity的onCreate() 里面调用setContentView,把一个xml设置为Activity的布局。或者在Dialog的Builder里面setView,把一个view设置给dialog作为内容视图。另外,还有往ViewGroup里addView(),添加子view的方法。这些方法里,Activity的setContentView,Dialog的setView,都是在控件生命周期执行过程中设置,这时view树还没有被构建,而ViewGroup的addView则往往是view树已经构建并且显示在窗口上之后被调用。要了解ViewGroup的addView() 的流程,绕不过了解view树从无到有的过程。所以首先还是看看 Activity.setContentView():
一、Activity.setContentView():
public class Activity extends ContextThemeWrapper
public void setContentView(View view) {
getWindow().setContentView(view);
……
}
}
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
ViewGroup mContentParent;
private LayoutInflater mLayoutInflater;
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
……
installDecor();
……
mLayoutInflater.inflate(layoutResID, mContentParent);
……
}
private void installDecor() {
if (mDecor == null) {
//1.跳过Context的获取过程,简单知道在这里是创建了一个DecorView
mDecor = new DecorView(context, -1, this, getAttributes());
……
} else {
//2.绑定window
mDecor.setWindow(this);
}
if (mContentParent == null) {
//3.初始化布局视图,包括decorView和展示开发人员指定的view的content布局
mContentParent = generateLayout(mDecor);
//……省略有titleContent布局的情况
}
}
}
Activity的setContentView()方法中是调用的getWindow()方法的setContentView。getWindow()返回的是Window的实现类是PhoneWindow。在PhoneWindow里,有两个与view绘制息息相关的成员变量:
- DecorView mDecor :是Window的顶层视图,分为title和content两个部分
- ViewGroup mContentParent :这是放置窗口内容的视图,是mDecor的子view,对应布局文件中R.id.content的节点,是个FramLayout。
- LayoutInflater mLayoutInflater; // 先跳过,分析完mDecor 和mContentParent再看。
PhoneWindow.setContentView 主要就做了两件事情:
- 初始化 decor——installDecor()
- 先创建decorView (new DecorView)
- 然后将decorView和window绑定,
- 最后generateLayout()——初始化decorview,并且用开发人员传入的layoutResId初始化mContentParent 。
- mLayoutInflater.inflate() // 先跳过,分析完mDecor 和mContentParent再看。
先简单看看创建decorView 的创建和mContentParent 的初始化过程generateLayout() :
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//这个id在 main_layout 会有,用来存放decorView中的content部分
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
protected ViewGroup generateLayout(DecorView decor) {
final Context context = getContext();
// layoutResource是布局资源id,它就是整个Activity的布局,
// 其中含有title区域和content区域,
// content区域就是用来显示通过setContentView设置进来的内容区域,也就是要显示的视图
int layoutResource;
//会由SDK等属性选择不同的顶层视图布局,如FEATURE_NO_TITLE则选择没有title的布局文件等
//从代码里可以看到有非常多的布局文件,比如
layoutResource = new TypedValue().resourceId;
layoutResource = R.layout.screen_title_icons;
layoutResource = R.layout.screen_progress;
layoutResource = R.layout.screen_custom_title;
layoutResource = getWindowStyle().getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
layoutResource = R.layout.screen_title;
layoutResource = R.layout.screen_simple_overlay_action_mode;
layoutResource = R.layout.screen_simple;
//调用decorView的加载layoutResource的方法,调用完成后dicorView就包含了这个布局文件的view
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
//……省略一些对mDecor设置属性,设置title的代码
return contentParent;
}
}
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
private PhoneWindow mWindow;
ViewGroup mContentRoot;
DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) {
mWindow = phoneWindow;
}
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
final View root = inflater.inflate(layoutResource, null);
//在没有标题试图的情况下,layoutResource 的视图会直接添加到decorView的子view
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) root;
}
}
generateLayout() 将layoutResId这个布局的视图附加到 contentParent中,这个layoutResId就是开发人员在onCreate函数中设置给Activity的那个布局资源id,也这是说Google预先设置了一些布局资源,这些布局资源里面有一个留给开发者们设置窗口内容的区域,也就是content区域,我们通过setContentView设置的布局会被添加到content布局中——也就是mContentParent。
到 generateLayout()完成为止,以mDecor为根的视图就已经创建完成。
接下来就是前面跳过的,PhoneWindow.setContentView() 做的第二件事情:mLayoutInflater.inflate() 。LayoutInflactor能够将布局XML文件实例化为其相应的视图对象,是理解android UI渲染非常重要的一环。LayoutInflactor 是在PhoneWindow 的构造函数中被初始化的,LayoutInflater.inflactor() 在setContentView() 中被调用。
public abstract class LayoutInflater {
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
//参数1为xml解析器,参数2为要解析布局的父视图,参数3为是否将要解析的视图添加到父视图中
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
}
LayoutInflater.inflate() 有三个参数参数1 @LayoutRes int resource 为xml解析器,参数2 @Nullable ViewGroup root 为要解析布局的父视图,参数3 boolean attachToRoot 为是否将要解析的视图添加到父视图中。root为NULL的时候默认不添加到父视图,不为NULL则默认添加。
public class LayoutInflater extends LayoutInflater {
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
……
final String name = parser.getName();
//…… 跳过单独处理merge标签部分
// Temp 是xml里面的root
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
// Inflate xml里面的root节点下的所有子节点
rInflateChildren(parser, temp, attrs, true);
// 把xml添加到root
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
}
上述的inflate方法中,主要有下面几步:
(1)解析xml中的根标签(第一个元素);
(2)如果根标签是merge,那么调用rInflate进行解析,rInflate会将merge标签下的所有子View直接添加到根标签中;
(3)如果标签是普通元素,调用createViewFromTag()对该元素进行解析;
(4)调用rInflate() 解析temp根元素下的所有子View,并且将这些子View都添加到temp下;
(5)返回解析到的根视图。
createViewFromTag() 根据完整路径的类名通过反射机制构造View对象.
rInflate()负责解析temp视图下的所有子View —— 我们的窗口中是一棵视图树,LayoutInflater需要解析完这棵树,这个功能就交给了rInflate方法。rInflate通过深度优先遍历来构造视图树,每解析到一个View元素就会递归调用rInflate,直到这条路径下的最后一个元素,然后再回溯过来将每个View元素添加到它们的parent中—— root.addView():
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
private View[] mChildren;
// mChildern 数组里有效子view的个数,剩余的是null或者无效。
private int mChildrenCount;
public void addView(View child, int index, LayoutParams params) {
// addViewInner() ,当子view设置新的 LayoutParams里会调用 child.requestLayout() 。但是这一层会自己先调用 requestLayout(),所以 child's request will be blocked at our level
requestLayout();
invalidate(true);
// 根据 index mChildrenCount children.length 分情况将 child 加入到数组对应index下
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
……
addInArray(child, index);//把view添加到mChildren 数组里面
}
}
通过rInflate的解析之后,整棵视图树就构建完毕,所有的view以树的结构存储在viewGroup的view数组 mChildren 里面——(再之后,无论是事件分发,还是绘制,都以mChildren 这个view数组为基础,也就是以view的树形数据结构为基础),setContentView() 的过程终于解暑了!根view添加到mContentParent里(最开始setContentView() 里传入的)。
到这里,PhoneWindow 里的mDecorView已经准备好了,但是还没有显示到屏幕上。所以到底什么时候显示?当然是看生命周期啦!看看onResume做了什么:
public final class ActivityThread extends ClientTransactionHandler {
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
boolean reallyResume) {
// 1.performResumeActivity() 最终调用Activity的onResume方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
// 2.获取了Activity的Window
r.window = r.activity.getWindow();
// 3.获取了Window的DecorView,也就是最顶级的视图
View decor = r.window.getDecorView();
// 4.将DecorView先隐藏
decor.setVisibility(View.INVISIBLE);
// 5.获取WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
// 将DecorView添加到窗口中
wm.addView(decor, l);
}
}
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
// 代码省略
if (r.activity.mVisibleFromClient) {
// 使得Activity变得可见,其实是设置DecorView变为可见
r.activity.makeVisible();
}
}
// 最后通知ActivityManagerNative,该Activity己经变为resume状态
if (reallyResume) {
try {
ActivityManagerNative.getDefault().activityResumed(token);
} catch (RemoteException ex) {
}
}
}
}
}
handleResumeActivity()这个函数里,先通过performResumeActivity() 去调用了Activity里面开发人员写的那个onResume() 方法,然后看上面 2.3.4!正是我们上面跟了半天的PhoneWindow和它的mDecorView啊!再接着,出现了一个新的朋友,WindowManager, handleResumeActivity() 里把熟悉的DecorView添加到WindowManager了,最后将Activity的DecorView设置为可见,并且通知
ActivityManagerService渲染视图,因此,在onResume函数之后,Activity就显示在屏幕上了。
新朋友WindowManager是个新的世界,另起一篇跟代码。为了巩固这个setContentView() - PhoneWindow-mDecorView 的流程,我们简单看看Dialog。
二、Dialog的setView
当我们要弹出一个Dialog,调用代码一般是这么写的:
View MYVIEW = ……;
AlertDialog popup = new AlertDialog.Builder(getContext()).setView(MYVIEW).create();
popup.show();
AlertDialog 同样是有一个mDecorView作为跟布局,在builder中设置,最后show的时候才会触发生命周期。
public class Dialog {
final Window mWindow;
View mDecor;
public void show() {
……
if (!mCreated) {
// 4.执行onCreate生命周期
dispatchOnCreate(null);
} else {
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
// 5. 获取DecorView 并 将mDecor添加到WindowManager中
mDecor = mWindow.getDecorView();
……
mWindowManager.addView(mDecor, l);
……
}
}
在show函数中主要做了如下几个事情:
(1)通过dispatchOnCreate函数来调用AlertDialog的onCreate函数;
(2)然后调用AlertDialog的onStart函数;
(3)最后将Dialog的DecorView添加到WindowManager中。
onCreate() 调用了 AlertController 的installContent(),初始化AlertDialog布局中的各个部分,包括id为custom的节点,在installContent()调用之后整个 Dialog 的视图内容全部设置到布局中。onStart() 结束之后,mWindowManager.addView(mDecor, l);WindowManager会将Window对象的DecorView添加到用户的窗口上,并且显示出来。
看吧,和Activity 手法就是一摸一样,也到了WindowManager的部分。
总结
1.Activity和Dialog都持有一个Window对象,PhoneWindow最重要的是持有了一个mDecorView。(PhoneWindow 并不具备多少 View 相关的能力)
2.用户设置给activity的xml,最终会通过LayoutInflactor解析,成为一个个的view节点,存储在viewgroup的view数组 mChildren里面,这是一个树的数据结构。
3.当Activity和Dialog生命周期走到onResume时,PhoneWindow的 mDecorView会被设置给WindowManager。
[ AndroidUI渲染专题其他内容 ]
(三)surface