Android中View创建的流程

来源:互联网 发布:php程序员的工作常态 编辑:程序博客网 时间:2024/06/11 12:51

一、了解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), 源码如下

@Overridepublic 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的视图就会显示出来了。

原创粉丝点击