@SystemService(Context.LAYOUT_INFLATER_SERVICE)public abstract class LayoutInflater {... ...}static { ... ...registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,                new CachedServiceFetcher() {            @Override            public LayoutInflater createService(ContextImpl ctx) {//创建LayoutInlater,具体类是PhoneLayoutInflater                 return new PhoneLayoutInflater(ctx.getOuterContext());            }}); ... ...}public class PhoneLayoutInflater extends LayoutInflater {   //内置View类型的前缀,如TextView的完整路径是android.widget.TextView    private static final String[] sClassPrefixList = {        "android.widget.",        "android.webkit.",        "android.app."    };    ... ...}/** Override onCreateView to instantiate names that correspond to the    widgets known to the Widget factory. If we don't find a match,    call through to our super class.重写onCreateView以 实例化 与之对应的名称 (Widget 工厂所了解的)。 如果我们找不到匹配,请通过父类。*/@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {  //在View名字的前面添加前缀来构造View的完整路径,例如,类名为TextView,那么TextVuiew完整的路径是android.widget.TextView    for (String prefix : sClassPrefixList) {        try {            View view = createView(name, prefix, attrs);            if (view != null) {                return view;            }        } catch (ClassNotFoundException e) {            // In this case we want to let the base class take a crack            // at it.        }    }    return super.onCreateView(name, attrs);}

 

代码不多,核心是覆写了LayoutInflater的onCreateView方法,该方法就是在传递进来的View的名字上加上“android.widget.”或者"android.webkit."前缀用以得到该内置View类(如TextView、Button等都在android.widget包下)的完整路径。最后根据类的完整路径来构造对应的View对象。

具体是一个怎样的流程?以Activity 的setContentView为例:

public class Activity extends ContextThemeWrapper        implements LayoutInflater.Factory2,        Window.Callback, KeyEvent.Callback,        OnCreateContextMenuListener, ComponentCallbacks2,        Window.OnWindowDismissedCallback, WindowControllerCallback,        AutofillManager.AutofillClient {        ... ...       }/** * Set the activity content from a layout resource.  The resource will be * inflated, adding all top-level views to the activity. * * @param layoutResID Resource ID to be inflated. * * @see #setContentView(android.view.View) * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) */public void setContentView(@LayoutRes int layoutResID) {    getWindow().setContentView(layoutResID);    initWindowDecorActionBar();}/** * Set the activity content to an explicit view.  This view is placed * directly into the activity's view hierarchy.  It can itself be a complex * view hierarchy.  When calling this method, the layout parameters of the * specified view are ignored.  Both the width and the height of the view are * set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use * your own layout parameters, invoke * {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)} * instead. * * @param view The desired content to display. * * @see #setContentView(int) * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) */public void setContentView(View view) {    getWindow().setContentView(view);    initWindowDecorActionBar();}

 

Activity的setContentView方法实际调用的是Window的setContentView,而Window是一个抽象类,上文提到的Window的具体实现类是PhoneWindow。

@Overridepublic void setContentView(int layoutResID) {    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window    // decor, when theme attributes and the like are crystalized. Do not check the feature    // before this happens.    if (mContentParent == null) {       //1.当mContentparent为空时先构建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,通过inflate函数将指定的布局视图添加到mContentarent中        mLayoutInflater.inflate(layoutResID, mContentParent);    }    mContentParent.requestApplyInsets();    final Callback cb = getCallback();    if (cb != null && !isDestroyed()) {        cb.onContentChanged();    }    mContentParentExplicitlySet = true;}

 

 

 

从上图发现:

我们发现mDecor中会加载一个系统定义好的布局,这个布局中又包裹了mContentParent,而这个mContentParent就是我们设置的布局,并添加到parent区域。

在PhoneWindow的setContentView中验证了这一点,首先会构建mContentParent对象,然后通过LayoutInflater的inflate函数将指定布局的视图添加到mContentParent中。

 /**     * Inflate a new view hierarchy from the specified xml resource. Throws     * {@link InflateException} if there is an error.     *     * @param resource ID for an XML layout resource to load (e.g.,     *        R.layout.main_page)     * @param root Optional view to be the parent of the generated hierarchy.     * @return The root View of the inflated hierarchy. If root was supplied,     *         this is the root View; otherwise it is the root of the inflated     *         XML file.     */    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {        return inflate(resource, root, root != null);    }    /**     * Inflate a new view hierarchy from the specified xml node. Throws     * {@link InflateException} if there is an error. *     * 

* ImportantFor performance * reasons, view inflation relies heavily on pre-processing of XML files * that is done at build time. Therefore, it is not currently possible to * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. * * @param parser XML dom node containing the description of the view * hierarchy. * @param root Optional view to be the parent of the generated hierarchy. * @return The root View of the inflated hierarchy. If root was supplied, * this is the root View; otherwise it is the root of the inflated * XML file. */ public View inflate(XmlPullParser parser, @Nullable ViewGroup root) { return inflate(parser, root, root != null); } /** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. * * @param resource ID for an XML layout resource to load (e.g., * R.layout.main_page) * @param root Optional view to be the parent of the generated hierarchy (if * attachToRoot is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if attachToRoot is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + Integer.toHexString(resource) + ")"); } final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } /** * Inflate a new view hierarchy from the specified XML node. Throws * {@link InflateException} if there is an error. *

* ImportantFor performance * reasons, view inflation relies heavily on pre-processing of XML files * that is done at build time. Therefore, it is not currently possible to * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. * * @param parser XML dom node containing the description of the view * hierarchy. * @param root Optional view to be the parent of the generated hierarchy (if * attachToRoot is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if attachToRoot is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ #LayoutInflater //参数1 为xml解析器 参数2 为要解析布局的父视图 参数3为是否将要解析的视图添加到父视图中//这里使用的是android的XmlPullParser解析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]; //Context对象 mConstructorArgs[0] = inflaterContext; //存储父视图 View result = root; try { // Look for the root node. int type; //找到root元素 while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } ... ... final String name = parser.getName(); ... ... //1. 解析merge标签 if (TAG_MERGE.equals(name)) { ... ... rInflate(parser, root, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml //2.不是merge标签直接解析布局中的视图 //3.这里通过xml的tag来解析layout根视图 //name就是要解析的视图的类名,如RelativeLayout final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { ... ... // Create layout params that match root, if supplied // 生成布局参数 params = root.generateLayoutParams(attrs); //如果attachToRoot为false,那么给temp设置布局参数 if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } // Inflate all children under temp against its context. //解析temp视图下所有的子View rInflateChildren(parser, temp, attrs, true); // We are supposed to attach all the views we found (int temp) // to root. Do that now. //如果Root不为空,且attachToRoot为true,那么将temp添加到父视图中 if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. //如果root为空或者attachToRoot为false,那么返回结果就是temp if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { ... ... } return result; }}

 

上述的inflate方法中,主要有以下几步:

1.解析xml中的根标签(第一个元素)

2.如果根标签是merge,那么调用rInflate进行解析,rInflate会将merge标签下的所有子VIew直接添加到根标签中。

3.如果标签是普通元素,那么运行到代码3,调用createViewFromTag对该元素进行解析。

4.调用rInflate解析temp根元素下的所有的子View,并且将这些子View都添加到temp下;

5.返回解析到的根视图。

 

解析单个元素的createViewFromTag方法/** * Convenience method for calling through to the five-arg createViewFromTag * method. This method passes {@code false} for the {@code ignoreThemeAttr} * argument and should be used for everything except {@code >include>} * tag parsing. */private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {    return createViewFromTag(parent, name, context, attrs, false);}View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,        boolean ignoreThemeAttr) {    if (name.equals("view")) {        name = attrs.getAttributeValue(null, "class");    }    // Apply a theme wrapper, if allowed and one is specified.    if (!ignoreThemeAttr) {        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);        final int themeResId = ta.getResourceId(0, 0);        if (themeResId != 0) {            context = new ContextThemeWrapper(context, themeResId);        }        ta.recycle();    }    if (name.equals(TAG_1995)) {        // Let's party like it's 1995!        return new BlinkLayout(context, attrs);    }    try {        View view;        //1.用户可以通过设置LayoutInflater的factory来自行解析View,默认这些Factory都为空,可以忽略这段        if (mFactory2 != null) {            view = mFactory2.onCreateView(parent, name, context, attrs);        } else if (mFactory != null) {            view = mFactory.onCreateView(name, context, attrs);        } else {            view = null;        }        if (view == null && mPrivateFactory != null) {            view = mPrivateFactory.onCreateView(parent, name, context, attrs);        }//2.没有Factory的情况下通过onCreateView或者createView创建View        if (view == null) {            final Object lastContext = mConstructorArgs[0];            mConstructorArgs[0] = context;            try {//3.内置View控件的解析                if (-1 == name.indexOf('.')) {                    view = onCreateView(parent, name, attrs);                } else {                   //4.自定义控件的解析                    view = createView(name, null, attrs);                }            } finally {                mConstructorArgs[0] = lastContext;            }        }        return view;    } catch (InflateException e) {        ... ...    }       }

 

本程序的重点在于代码2,以及以后的代码,createViewFromTag将该元素的parent及名字传递过来。

区分内置View和自定义View的方式:

当这个tag的名字中没有包含“.”(在名字中查找“.”返回-1)时,LayoutInflater会认为这是一个内置的View。

例:

 

 

这里的FrameLayout就是xml元素的名字,因此在执行inflate时就会调用3处的onCreateView来解析这个FrameLayout标签。当我们使用自定义View时,在xml中必须写View的完整路径。

 

此时调用代码注释的4的createView来解析该View。

在上文的PhoneLayoutInflater中,PhoneLayoutInflater覆写了onCreateView方法,也就是代码3处的onCreateView方法,该方法就是在View的标签名的前面设置一个"android.widget."前缀,然后传递给createView进行解析。

也就是内置View 和 自定义 View最终都调用了createView进行解析,只是Google为了让开发者在xml中更方便定义View,只写View名称而不需要写完整的路径。

在LayoutInflater解析时,如果遇到只写类名的View,那么认为是内置的View控件,在onCreateView中将"android.widget."前缀传递给craeteView方法。

最后在crateView中构造View 的完整路径来解析。

如果是自定义控件,那么必须写完整路径,此时调用createView且前缀为null进行解析。

//createView相关代码//根据完整路径的类名通过反射机制构造View对象public final View createView(String name, String prefix, AttributeSet attrs)        throws ClassNotFoundException, InflateException {    //1.从缓存中获取构造函数    Constructor<? extends View> constructor = sConstructorMap.get(name);    if (constructor != null && !verifyClassLoader(constructor)) {        constructor = null;        sConstructorMap.remove(name);    }    Class<? extends View> clazz = null;    try {        //2.没有缓存构造函数        if (constructor == null) {            // Class not found in the cache, see if it's real, and try to add it            //如果prefix不为空,那么构造完整的View路径,并且加载该类            clazz = mContext.getClassLoader().loadClass(                    prefix != null ? (prefix + name) : name).asSubclass(View.class);            if (mFilter != null && clazz != null) {                boolean allowed = mFilter.onLoadClass(clazz);                if (!allowed) {                    failNotAllowed(name, prefix, attrs);                }            }            //3.从Class对象获取构造函数            constructor = clazz.getConstructor(mConstructorSignature);            constructor.setAccessible(true);            //4.将构造函数存入缓存中            sConstructorMap.put(name, constructor);        } else {            ... ...        }        Object lastContext = mConstructorArgs[0];        if (mConstructorArgs[0] == null) {            // Fill in the context if not already within inflation.            mConstructorArgs[0] = mContext;        }        Object[] args = mConstructorArgs;        args[1] = attrs;        //5.通过反射构造View        final View view = constructor.newInstance(args);        if (view instanceof ViewStub) {            // Use the same context when inflating ViewStub later.            final ViewStub viewStub = (ViewStub) view;            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));        }        mConstructorArgs[0] = lastContext;        return view;    } catch (NoSuchMethodException e) {        ... ...    }}

 

 

 

createView相对比较简单,如果有前缀,那么构造View的完整路径,并且将该类加载到虚拟机中,然后获取该类的构造函数并缓存起来,再通过构造函数来创建View的对象,最后将View对象返回,这就是解析单个View的过程。

而我们的窗口是一棵视图树,LayoutInflater需要解析这棵树,这个功能就交给了rInflate方法。

/** * Recursive method used to descend down the xml hierarchy and instantiate * views, instantiate their children, and then call onFinishInflate(). * 

* Note: Default visibility so the BridgeInflater can * override it. */void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { //1.获取树的深度,深度优先遍历 final int depth = parser.getDepth(); int type; boolean pendingRequestFocus = false; //挨个元素解析 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { pendingRequestFocus = true; consumeChildElements(parser); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) {//解析 include 标签 if (parser.getDepth() == 0) { throw new InflateException(" cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) {// 解析merge标签,抛出异常,因为merge标签必须为根视图。 throw new InflateException(" must be the root element"); } else { //3.根据元素名进行解析 final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); //递归调用进行解析,也就是深度优先遍历 rInflateChildren(parser, view, attrs, true); //将解析到的View添加到ViewGroup中,也就是其parent viewGroup.addView(view, params); } } if (pendingRequestFocus) { parent.restoreDefaultFocus(); } if (finishInflate) { parent.onFinishInflate(); }}

 

 

rInflate 通过深度优先遍历,每解析一个View元素就会递归调用rInflate,直到这条路径下的最后一个元素,然后再回溯过来将每个View元素添加到它们的parent中。通过rInflate的解析之后,整棵视图树就构建完毕。当调用了activity的onResume()之后,我们通过steContentView设置的内容就会出现在视野中。

 

 

 

 

 

 

使用深度优先搜索来遍历这个图的具体过程是:

首先从一个未走到过的顶点作为起始顶点,比如1号顶点作为起点。

沿1号顶点的边去尝试访问其它未走到过的顶点,首先发现2号顶点还没有走到过,于是来到了2号顶点。

再以2号顶点作为出发点继续尝试访问其它未走到过的顶点,这样又来到了4号顶点。

再以4号顶点作为出发点继续尝试访问其它未走到过的顶点。

但是,此时沿4号顶点的边,已经不能访问到其它未走到过的顶点了,所以需要返回到2号顶点。

返回到2号顶点后,发现沿2号顶点的边也不能再访问到其它未走到过的顶点。此时又会来到3号顶点(2->1->3),再以3号顶点作为出发点继续访问其它未走到过的顶点,于是又来到了5号顶点。

至此,所有顶点我们都走到过了,遍历结束。

 

参考《Android源码设计模式》

 

 

深度优先遍历的主要思想是:

1.首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;

2.当没有未访问过的顶点时,则回到上一个顶点,继续试探别的顶点,直到所有的顶点都被访问过。

 

 

更多相关文章

  1. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. android笔记4-xml解析
  3. No 93 · android xml的生成和解析
  4. Android日历周视图 可添加事件标记
  5. AsyncTask 源码解析
  6. 三、ANDROID SDK下文件解析
  7. 探究Android界面的显示机制
  8. 【Android工具】被忽略的UI检视利器:Hierarchy Viewer
  9. Android学习系列(19)-App数据格式之解析Xml

随机推荐

  1. Windows下Qt for Android(安卓)编译安卓C
  2. android使用透明色
  3. Android(安卓)Context 上下文 你必须知道
  4. Android(安卓)RxJava实际应用教学:你该什
  5. Android四大组件:Service史上最全面解析
  6. Android实践之Drawable的使用
  7. Android学习感悟之消息机制
  8. android 通过Application类 共享全局数据
  9. Android(安卓)9.0 (P)
  10. 信息提醒之Notification,兼容全部SDK-更新