在上篇文章中我们学习了setContentView的源码,还记得其中的LayoutInflater吗?本篇文章就来学习下LayoutInflater。

    @Override    public void setContentView(int resId) {        ensureSubDecor();        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);        contentParent.removeAllViews();        LayoutInflater.from(mContext).inflate(resId, contentParent);        mOriginalWindowCallback.onContentChanged();    }

备注:本文基于 Android 8.1.0。

1、LayoutInflater 简介

Instantiates a layout XML file into its corresponding View objects. It is never used directly. Instead, use Activity.getLayoutInflater() or Context.getSystemService(Class) to retrieve a standard LayoutInflater instance that is already hooked up to the current context and correctly configured for the device you are running on.

翻译过来就是:LayoutInflater 的作用就是将XML布局文件实例化为相应的 View 对象,需要通过Activity.getLayoutInflater() 或 Context.getSystemService(Class) 来获取与当前Context已经关联且正确配置的标准LayoutInflater。

总共有三种方法来获取 LayoutInflater:

  1. Activity.getLayoutInflater();
  2. Context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) ;
  3. LayoutInflater.from(context);

事实上,这三种方法之间是有关联的:

  • Activity.getLayoutInflater() 最终会调用到 PhoneWindow 的构造方法,实际上最终调用的就是方法三;
  • 而方法三最终会调用到方法二 Context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) ;

2、inflate 方法解析

Android LayoutInflater 源码解析_第1张图片 image

LayoutInflater 的 inflate 方法总共有四个,属于重载的关系,最终都会调用到 inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) 方法。

备注:以下源码中有七条备注。

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {        synchronized (mConstructorArgs) {            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");            final Context inflaterContext = mContext;            final AttributeSet attrs = Xml.asAttributeSet(parser);            Context lastContext = (Context) mConstructorArgs[0];            mConstructorArgs[0] = inflaterContext;            View result = root;            try {                // Look for the root node.                int 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!");                }                final String name = parser.getName();                if (TAG_MERGE.equals(name)) {                // ② 如果是Merge标签,则必须依附于一个RootView,否则抛出异常                    if (root == null || !attachToRoot) {                        throw new InflateException(" can be used only with a valid "                                + "ViewGroup root and attachToRoot=true");                    }                    rInflate(parser, root, inflaterContext, attrs, false);                } else {                    // Temp is the root view that was found in the xml                    // ③ 根据节点名来创建View对象                     final View temp = createViewFromTag(root, name, inflaterContext, attrs);                    ViewGroup.LayoutParams params = null;                    if (root != null) {                        if (DEBUG) {                            System.out.println("Creating params from root: " +                                    root);                        }                        // Create layout params that match root, if supplied                        // ④ 如果设置的Root不为null,则根据当前标签的参数生成LayoutParams                        params = root.generateLayoutParams(attrs);                        if (!attachToRoot) {                            // Set the layout params for temp if we are not                            // attaching. (If we are, we use addView, below)                            // ⑤ 如果不是attachToRoot ,则对这个Tag和创建出来的View设置LayoutParams;注意:此处的params只有当被添加到一个Viewz中的时候才会生效;                            temp.setLayoutParams(params);                        }                    }                    if (DEBUG) {                        System.out.println("-----> start inflating children");                    }                    // Inflate all children under temp against its context.                    // ⑥ inflate children tag                    rInflateChildren(parser, temp, attrs, true);                    // We are supposed to attach all the views we found (int temp)                    // to root. Do that now.                    if (root != null && attachToRoot) {                        // ⑦ 如果Root不为null且是attachToRoot,则添加创建出来的View到Root 中                        root.addView(temp, params);                    }                    // Decide whether to return the root that was passed in or the                    // top view found in xml.                    if (root == null || !attachToRoot) {                        result = temp;                    }                }            } catch (XmlPullParserException e) {                ......            }            return result;        }    }

备注:根据以上源码,我们也可以分析出来 inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) 不同参数值带来的影响:

  1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义;
  2. 如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root;
  3. 如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效;
  4. 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true;

3、rInflate 方法解析

以上代码中我们还有两个方法没有分析:rInflate 和 rInflateChildren ;而 rInflateChildren 实际上是调用了rInflate;

备注:以下源码中有六条备注。

    void rInflate(XmlPullParser parser, View parent, Context context,            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {        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)) {                if (parser.getDepth() == 0) {                // ① 如果这里出现了include标签,就会抛出异常                    throw new InflateException(" cannot be the root element");                }                parseInclude(parser, context, parent, attrs);            } else if (TAG_MERGE.equals(name)) {                                // ② 同理如果这里出现了merge标签,也会抛出异常                throw new InflateException(" must be the root element");            } else {                // ③ 最重要的方法在这里,createViewFromTag                final View view = createViewFromTag(parent, name, context, attrs);                final ViewGroup viewGroup = (ViewGroup) parent;                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);                // ④如果当前View是ViewGroup(包裹了别的View)则在此处inflate其所有的子View                rInflateChildren(parser, view, attrs, true);                // ⑤添加inflate出来的view到parent中                viewGroup.addView(view, params);            }        }        if (pendingRequestFocus) {            parent.restoreDefaultFocus();        }        if (finishInflate) {            // ⑥如果inflate结束,则回调parent的onFinishInflate方法            parent.onFinishInflate();        }    }

总结:

  • 首先进行View的合理性校验,include、merge等标签;
  • 通过 createViewFromTag 创建出 View 对象;
  • 如果是 ViewGroup,则重复以上步骤;
  • add View 到相应的 parent 中;

4、createViewFromTag 方法解析

备注:以下源码中有六条备注。

    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;            if (mFactory2 != null) {                // ① 有mFactory2,则调用mFactory2的onCreateView方法                view = mFactory2.onCreateView(parent, name, context, attrs);            } else if (mFactory != null) {                // ② 有mFactory,则调用mFactory的onCreateView方法                view = mFactory.onCreateView(name, context, attrs);            } else {                view = null;            }            if (view == null && mPrivateFactory != null) {                // ③ 有mPrivateFactory,则调用mPrivateFactory的onCreateView方法                view = mPrivateFactory.onCreateView(parent, name, context, attrs);            }            if (view == null) {                // ④ 走到这步说明三个Factory都没有,则开始自己创建View                final Object lastContext = mConstructorArgs[0];                mConstructorArgs[0] = context;                try {                    if (-1 == name.indexOf('.')) {                        // ⑤ 如果View的name中不包含 '.' 则说明是系统控件,会在接下来的调用链在name前面加上 'android.view.'                        view = onCreateView(parent, name, attrs);                    } else {                        // ⑥ 如果name中包含 '.' 则直接调用createView方法,onCreateView 后续也是调用了createView                        view = createView(name, null, attrs);                    }                } finally {                    mConstructorArgs[0] = lastContext;                }            }            return view;        } catch (InflateException e) {            throw e;        }     }

总结:

  • createViewFromTag 方法比较简单,首先尝试通过 Factory 来创建View;
  • 如果没有 Factory 的话则通过 createView 来创建View;

5、createView 方法解析

备注:以下源码中有三条备注。

    public final View createView(String name, String prefix, AttributeSet attrs)            throws ClassNotFoundException, InflateException {        Constructor<? extends View> constructor = sConstructorMap.get(name);        if (constructor != null && !verifyClassLoader(constructor)) {            constructor = null;            sConstructorMap.remove(name);        }        Class<? extends View> clazz = null;        try {            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);            if (constructor == null) {                // Class not found in the cache, see if it's real, and try to add it                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);                    }                }                // ① 反射获取这个View的构造器                constructor = clazz.getConstructor(mConstructorSignature);                constructor.setAccessible(true);                // ② 缓存构造器                sConstructorMap.put(name, constructor);            } else {                // If we have a filter, apply it to cached constructor                if (mFilter != null) {                    // Have we seen this name before?                    Boolean allowedState = mFilterMap.get(name);                    if (allowedState == null) {                        // New class -- remember whether it is allowed                        clazz = mContext.getClassLoader().loadClass(                                prefix != null ? (prefix + name) : name).asSubclass(View.class);                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);                        mFilterMap.put(name, allowed);                        if (!allowed) {                            failNotAllowed(name, prefix, attrs);                        }                    } else if (allowedState.equals(Boolean.FALSE)) {                        failNotAllowed(name, prefix, attrs);                    }                }            }            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;            // ③ 使用反射创建 View 对象,这样一个 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 (ClassCastException e) {        }     }

总结:

  • createView 方法也比较简单,通过反射来创建的 View 对象;

6、总结

通过本文我们学习到 LayoutInflater 创建 View的过程,也知道了 inflate 方法不同参数的意义,以及开发中遇到的一些异常在源码中的根源。可以看到从布局中 inflate 一个个具体的 View 的过程其实也很简单:

  • 通过 XML 的 Pull 解析方式获取 View 的标签;
  • 通过标签以反射的方式来创建 View 对象;
  • 如果是 ViewGroup 的话则会对子 View 遍历并重复以上步骤,然后 add 到父 View 中;
  • 与之相关的几个方法:inflate ——》 rInflate ——》 createViewFromTag ——》 createView ;

参考

  • 《Android LayoutInflater原理分析,带你一步步深入了解View(一)》
  • LayoutInflater

广告时间

今日头条各Android客户端团队招人火爆进行中,各个级别和应届实习生都需要,业务增长快、日活高、挑战大、待遇给力,各位大佬走过路过千万不要错过!

本科以上学历、非频繁跳槽(如两年两跳),欢迎加我的微信详聊:KOBE8242011

Android LayoutInflater 源码解析_第2张图片 欢迎关注

更多相关文章

  1. 2012-7-20 android 图片叠加效果——两种方法
  2. Android右滑返回上一个界面的实现方法
  3. 系统关闭GPRS数据流量和 GPS的方法
  4. Android添加半透明操作提示的方法
  5. 一句话源码
  6. Android Activity中启动另一应用程序的方法
  7. Android ButterKnife 使用方法总结
  8. Android实现多线程断点下载的方法

随机推荐

  1. Android(安卓)Market更名 主推云端同步功
  2. Handler问题总结
  3. Android(安卓)robotium自动化测试
  4. Android(安卓)MediaPlayer和VideoView的
  5. BlueStacks App Player:在PC上运行Android
  6. Android退出整个应用的方法
  7. Android图片压缩质量参数Bitmap.Config R
  8. android手把手教你开发launcher(五)
  9. Android界面布局大集合(Fragment+ViewPag
  10. Android SDK 和Oralce 也有冲突