Android中View创建的流程
2017-08-08 15:27
357 查看
一、了解LayoutInflater
LAYOUT_INFLATER_SERVICE与 AMS 和 WS 一样都是属于Android Framework层的服务,它们都是单例存在的,第一次加载ContextImpl的时候会将LayoutInflater的ServiceFetcher注入容器中。/** * 该方法在ContextImpl中, 作用是将服务注入Service容器的Map中, 只有第一次创建ContextImpl时执行 * 创建Activity时其对应的ContextImpl对象就会被创建, 它是Context的具体实现 */ registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext); } }); //PolicyManager的makeNewLayoutInflater方法会调用Policy的makeNewLayoutInflater方法 public LayoutInflater makeNewLayoutInflater(Context context) { return new PhoneLayoutInflater(context); }
LayoutInflater是一个抽象类,我们通常通过:
LayoutInflater.from().inflate();这样的方式让LayoutInflater去加载我们在XML中定义的布局。
其中的from()方法即为我们获取LAYOUT_INFLATER_SERVICE的方式, 代码如下:
public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) //省略异常捕捉的代码.. return LayoutInflater; }
由此可知LayoutInflater的实例是通过getSystemService(Context.LAYOUT_INFLATER_SERVICE)获取到的, 但是LayoutInflater是一个抽象类,具体实现它的子类是什么呢?从源码中可知,它的具体实现是PhoneLayoutInflater(),这个原理与Window和PhoneWindow的关系很像(源码可自行查阅)。getSystemService(Context.LAYOUT_INFLATER_SERVICE)方法返回的即为实现了LayoutInflater中抽象方法的PhoneLayoutInflater的实例。
二、View构建的流程
我们从最常见的Activity中的setContentView开始分析public void setContentView(int layoutResID) { //getWindow()获取的即为Window的实例 getWindow().setContentView(int layoutResID); initActionBar(); }
由源码可知, Activity中的setContentView会调用Window类下的setContentView方法。前面叙述过Window与LayoutInflater都是抽象的, 所以Window类下的setContentView方法的具体实现是为PhoneWindow中的setContentView(int layoutResID), 源码如下
@Override public void setContentView(int layoutResID) { //如果mContentParent没有被创建,那么将执行installDecor方法 //该方法会创建顶层的DecorView,以及其内部的mCotentParent。 if(mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } //解析我们传入的layoutResId(R.layout.XXXXX) //由此处可知,我们传入的xml布局文件将加入mContentParent中, //这也是为什么叫setContentView而不是setDecorView的原因 mLayoutInflater.inflate(layoutResID, mContentParent); }
PhoneLayoutInflater中的inflate方法有很多:
/** * @param resource:为我们定义的xml布局(R.layout.xxx) * @param root:可用于存放我们xml布局的父容器 */ public View inflate(int resource, ViewGroup root) { return inflate(resource, root, root != null); } /** * @param resource:为我们定义的xml布局(R.layout.xxx) * @param root:可用于存放我们xml布局的父容器 * @param attachToRoot:该boolean变量为是否将我们xml布局加入root中 */ public View inflate(int resource, ViewGroup root, boolean attachToRoot) { XmlResourceParser parser = getContext().getResources().getLayout(resource); try { inflate(parser, root, attachToRoot); } finally { parser.close(); } } /** * @param parser: 为xml解析器 * @param root: 可用于存放我们xml布局的父容器 * @param attachToRoot: 该boolean变量为是否将我们xml布局加入root中 */ public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot);
由上述的源码可知: 无论我们调用哪一种inflate方法,最终都会由第三的方法执行最终的操作,第三个方法即为View构建的核心之处。下面开始解析源码:
/** * @param parser: 为xml解析器 * @param root: 可用于存放我们xml布局的父容器 * @param attachToRoot: 该boolean变量为是否将我们xml布局加入root中 */ public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; //可用于存放我们xml布局的父容器 View result = root; try { int type; //1.通过XML解析器解析我们传入的xml布局中的顶层容器(根标签)信息,并把它赋给type while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } //解析xml布局中顶层容器(根标签)的控件名,例如(LinearLayout) final String name = parser.getName(); //2.判断xml中的顶层容器(根标签)是否为merge if (TAG_MERGE.equals(name)) { //如果为merge标签,则通过rInflate方法遍历该xml树,创建view实例 rInflate(parser, root, attrs, false); //3.若不是merge标签,则创建该顶层容器(根标签)的实例为temp } else { //创建根标签实例 final View temp = createViewFromTag(root, name, attrs); //声明一个布局参数 ViewGroup.LayoutParams params = null; if (root != null) { // 根据传入的root获取布局参数的信息 params = root.generateLayoutParams(attrs); // 则给temp设置布局参数 if (!attachToRoot) { temp.setLayoutParams(params); } } //遍历temp下所有的子视图,创建其实例 rInflate(parser, temp, attrs, true, true); //若root不为空且attachToRoot为true,则将xml布局加入root中 if (root != null && attachToRoot) { root.addView(temp, params); } //若root为空,或者attachToRoot为false,则我们的xml布局即相当于root,将其直接返回 //所以方法上面的注释中的root为:可用于存放我们xml布局的父容器, 若传入null的话, temp的LayoutParams将会无效, 因为LayoutParams是由root生成的 if (root == null || !attachToRoot) { result = temp; } } } //省略catch,finally代码... return result; } }
上述代码即为inflate的最终执行过程,笔者精简了很多代码,并且加了很多注册, 非常清晰, 总结一下主要分为以下几步:
(1) 解析到xml中的顶层容器(根标签)
(2)如果根标签为merge,根标签的parentView即为root,此时会直接调用rInflate进行解析, rInflate中会通过递归的方式使用深度优先搜索解析xml,创建其childView的实例,并且加入其对应的parentView中。
(3)如果根标签为普通标签,则会创建该根标签的实例,并且设置其布局参数,同样会调用rInflate进行解析。若attachToRoot为true且root不为null,则将该标签将会被加入root中,否则该根标签自身即为root。
上述代码中sInflate方法的作用是遍历xml中的view树创建实例且将其加入对应的parentView中。(篇幅限制就不再赘述了),但具体创建view的操作时交给谁执行的呢? 其实上面的源码中已经出现了,是createViewFromTag()该方法完成了View创建的重任, 下面贴出源码:
/** * @param parent: 该View的parent * @param name: 该View的Name(LinearLayout) * @param attrs: 该View的属性值 */ View createViewFromTag(View parent, String name, AttributeSet attrs) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } try { //用户可以设置LayoutInflater的factory自行解析View,默认的Factory方法都为空, 忽略这一段 if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, viewContext, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, viewContext, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs); } //重点部分从这儿开始 //判断name中的是否存在'.',若不存在则说明是系统控件 //因为系统控件我们在xml中使用的时候不需要加前缀 if (view == null) { //解析系统控件 if (-1 == name.indexOf('.')) { //onCreateView方法即为PhoneLayoutInflater中的重写的方法 //会给当前的View补全name前缀再调用createView()方法 view = onCreateView(parent, name, attrs); } else { //解析用户自定义控件,createView中会通过name利用反射的机制去构造View对象 //不再赘述 view = createView(name, null, attrs); } } return view; } }
上述操作完成后,View的实例就已经创建好了, 但此时View虽有实例,并没有宽高。所以在Activity中的onCreate方法中去获取View的宽高是会报错的。接下来会调用onStart方法,并且在onResume方法之前将DecorView添加到WindowManager中,并且设置Activity为可见,然后通知AMS,该Activity已经变为resume状态了,使得系统能够渲染Activity的视图,至此Activity的视图就会显示出来了。
相关文章推荐
- Android 7.0 虚拟按键(NavigationBar)源码分析(一) 之 View的创建流程
- 浅谈Android之Activity Decor View创建流程介绍
- Android 7.0 虚拟按键(NavigationBar)源码分析 之 View的创建流程
- Android O: View的绘制流程(一): 创建和加载
- Android O: View的绘制流程(一): 创建和加载
- Android中View绘制流程
- android是如何创建一个view
- Android视图状态及重绘流程分析,带你一步步深入了解View(三)
- 用SurfaceView制作简单的android游戏 : 重力小球(1)--------创建游戏整体框架
- Android应用层View绘制流程与源码分析
- Android自定义View或ViewGroup的流程
- 深入理解 Android 之 View 的绘制流程(一)
- Android应用层View绘制流程与源码分析
- Android中View绘制流程以及invalidate()等相关方法分析
- Android RecyclerView适配器的基本工作流程
- Android View的事件分发流程总结
- AndroidView绘制流程分析及自定义View、ViewGroup进阶
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android中View绘制流程以及invalidate()等相关方法分析
- Android视图状态及重绘流程分析,带你一步步深入了解View(三)