Android 中LayoutInflater(布局加载器)之源码篇

来源:互联网 发布:大庆三打一网络直播 编辑:程序博客网 时间:2024/05/17 07:47

本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢
博客地址:http://blog.csdn.net/l540675759/article/details/78099609

前言

如果读者没有阅读过该系列博客,建议先阅读下博文说明,这样会对后续的阅读博客思路上会有一个清晰的认识。

Android 中LayoutInflater(布局加载器)系列博文说明


导航

Android 中LayoutInflater(布局加载器)系列博文说明

Android 中LayoutInflater(布局加载器)系列之介绍篇

Android 中LayoutInflater(布局加载器)系列之源码篇

Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法

Android 中LayoutInflater(布局加载器)源码篇之rInflate方法

Android 中LayoutInflater(布局加载器)源码篇之parseInclude方法

Android 中LayoutInflater(布局加载器)之实战篇


概述

(1)Activity 的 getSystemService的实现过程

(2)LayoutInflater 如果将布局资源转换为 View 的过程

(3)LayoutInflater的 Factory,Factory2是什么,在解析过程中的作用是什么?

(4)LayoutInflater 的 inflater 方法的各个参数的含义,不同的情况的含义


LayoutInflater的构造方法

    protected LayoutInflater(Context context) {        mContext = context;    }

这种是LayoutInflater常规的构造方法,将Context传入,最后生成的LayoutInflater与对应的Context相绑定。

    protected LayoutInflater(LayoutInflater original, Context newContext) {        mContext = newContext;        mFactory = original.mFactory;        mFactory2 = original.mFactory2;        mPrivateFactory = original.mPrivateFactory;        setFilter(original.mFilter);    }

而这种构造方法来说,只是复制原LayoutInflater的内容,然后将Context对象替换,一般来说只会在cloneInContext()方法中使用。


LayoutInflater#form()方法分析

根据介绍篇的内容,LayoutInflater在Android开发中一般是通过

context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);LayoutInflater.from(context);

因为第一种方式,已经是LayoutInflater介绍中声明获取的方式之一,那么这里我们看一下LayoutInflater#form的方法。

    public static LayoutInflater from(Context context) {        LayoutInflater LayoutInflater =                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);        if (LayoutInflater == null) {            throw new AssertionError("LayoutInflater not found.");        }        return LayoutInflater;    }

从源码上看,LayoutInflater#form()方法内部也是通过getSystemService()方法获得,那么接下来我们看一下context#getSystemService()这个方法:

    public abstract Object getSystemService(@ServiceName @NonNull String name);

发现这个只是一个抽象方法,而我们知道Activity也是Context的一个实现。

Activity#getSystemService()这个方法:

    @Override    public Object getSystemService(@ServiceName @NonNull String name) {        if (getBaseContext() == null) {            throw new IllegalStateException(                    "System services not available to Activities before onCreate()");        }        //获取WindowManager        if (WINDOW_SERVICE.equals(name)) {            return mWindowManager;        //系统的搜索框SearchManager        } else if (SEARCH_SERVICE.equals(name)) {            ensureSearchManager();            return mSearchManager;        }        return super.getSystemService(name);    }

从上面看到,在Activity中只处理了两种类型的服务,分别是获取WindowManager、获取SearchManager,那我们接着看其父类的SystemService()方法:

    @Override    public Object getSystemService(String name) {        //找到我们要的东西,注意这是个单例        if (LAYOUT_INFLATER_SERVICE.equals(name)) {            if (mInflater == null) {                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);            }            return mInflater;        }        return getBaseContext().getSystemService(name);    }

在Activity的父类即ContextThemeWrapper的getSystemService()方法中,我们发现了LayoutInflater的创建过程,从上面的代码我们可以看出:

  1. 每个Activity内包含的LayoutInflater是一个单例。

  2. Activity创建LayoutInflater时,是先使用最原始的BaseContext创建,然后在将Activity的父类ContextThemeWrapper的信息通过cloneInContext()方法与其绑定。

然后我们在看下LayoutInflater的cloneInContext的实现:

    public abstract LayoutInflater cloneInContext(Context newContext);

先看下,这个方法的介绍:

这里写图片描述

  1. 这个方法通过现有的LayoutInflater创建一个新的LayoutInflater副本,唯一变化的地方是指向不同的上下文对象。

  2. 在ContextThemeWrapper通过这个方法创建的新的LayoutInflater还包含了主题的信息。

在ContextThemeWrapper中使用cloneInContext是想将更多的信息,赋予LayoutInflater中,与其相互绑定。


Activity中LayoutInflater创建

对于Activity的LayoutInflater,其实在Activity创建之时就已经创建完成,但是这一块内容属于FrameWork层的内容,博主道行太浅了,只想带大家看下from这个方法的实现过程。

这里如果大家想了解可以参考下这篇文章

LayoutInflater源码解析

而Activity#getLayoutInflater方法:

    @NonNull    public LayoutInflater getLayoutInflater() {        return getWindow().getLayoutInflater();    }

这个Window对象即PhoneWindow,此时创建出来的LayoutInflater即PhoneLayoutInflater。

这里给大家看下PhoneLayoutInflater的cloneInContext()方法:

    public LayoutInflater cloneInContext(Context newContext) {        return new PhoneLayoutInflater(this, newContext);    }
    protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {        super(original, newContext);    }

可以发现PhoneLayoutInflater中cloneInContext()的实现,调用了第二个构造方法。

这里在Android Studio是无法查阅的,有条件的可以下载源码,如果下载源码麻烦,可以在这里查阅。

Android源码查看网址


将R.layout.xxx转换为View的过程分析

其实这个过程即LayoutInflater.inflater()这个过程:

    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();        }    }

在这个方法中,只是先拿到XmlResourceParser,用于后续节点的解析,我们接着往下看:

这里只看一些关键的信息,具体代码大家自行查看

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {        //》》》》》》》》》》》》》》》》》第一部分》》》》》》》》》》》》》》》》》》》            try {                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)) {                    if (root == null || !attachToRoot) {                        throw new InflateException("<merge /> can be used only with a valid "                                + "ViewGroup root and attachToRoot=true");                    }                    rInflate(parser, root, inflaterContext, attrs, false);                } else {         //》》》》》》》》》》》》》》》》》第三部分》》》》》》》》》》》》》》》》》》》                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);                    ViewGroup.LayoutParams params = null;                    if (root != null) {                        params = root.generateLayoutParams(attrs);                        if (!attachToRoot) {                            temp.setLayoutParams(params);                        }                    }                    rInflateChildren(parser, temp, attrs, true);                    if (root != null && attachToRoot) {                        root.addView(temp, params);                    }                    if (root == null || !attachToRoot) {                        result = temp;                    }                }            return result;        }    }

第一部分:

这里第一部分的内容,主要是一个XML文件的读取过程,这里有两个判断:

(1)遍历XML内容寻找XML标签的开始的标志或者文档结尾的标志才可以跳出循环。

(2)如果该XML没有开始的标识,则抛出异常。

下面给大家介绍下,几种常见的解析标识:

XmlPullParser.START_DOCUMENT                                    文档开始XmlPullParser.END_DOCUMENT                                      文档结束XmlPullParser.START_TAG                                         XML标签的开始XmlPullParser.END_TAG                                           XML标签的结束XmlPullParser.TEXT                                              XML标签的内容

第二部分

这部分的一开始先进行了Merge标签的检验,如果发现该节点是Merge,必须满足父View存在,并且与父View绑定的状态。

转换为代码:

root != null && attachToRoot ==true

这里Merge是减少布局层级存在的标签,通常和include标签一起使用,所以其必须存在父View,而且merge标签的内容必须与父View绑定。

这里调用rInflate()方法去解析Merge的标签,而rInflate()方法,在另一篇文章已经单独分析。

Android 中LayoutInflater(布局加载器)源码篇之rInflate方法


第三部分

我们再看一下第三部分的代码,代码中会有一些简要的说明:

         //》》》》》》》》》》》》》》》》》第三部分》》》》》》》》》》》》》》》》》》》                    //createViewFromTag是一个根据name来创建View的方法                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);                    ViewGroup.LayoutParams params = null;                    if (root != null) {                        params = root.generateLayoutParams(attrs);                        if (!attachToRoot) {                            temp.setLayoutParams(params);                        }                    }                    //解析子标签                    rInflateChildren(parser, temp, attrs, true);                    if (root != null && attachToRoot) {                        root.addView(temp, params);                    }                    if (root == null || !attachToRoot) {                        result = temp;                    }                }            return result;        }

将第三部分内容分拆一下主要分为以下几块内容:

  1. 排除标签为include,或者merge之后,就会通过createViewFromTag()方法来创建View

  2. root是inflater()方法的第二个参数,而attachToRoot是第三个参数,最后会根据这两个参数来决定返回的View

在这部分中,createViewFromTag()是根据name(名称),来创建View的一个方法。

由于createViewFromTag()方法的通用性,这块内容博主给单独拿出来,链接如下:

Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法

接下来,我们要介绍的是inflater()方法中的参数,到底有什么作用?

                    ViewGroup.LayoutParams params = null;                    //当Root存在                    if (root != null) {                        params = root.generateLayoutParams(attrs);                        if (!attachToRoot) {                            //设置View在父布局下Params                            temp.setLayoutParams(params);                        }                    }                    //遍历子节点                    rInflateChildren(parser, temp, attrs, true);                    //如果Root存在并且attachToRoot为true,即与父View绑定                    //这里在解析的同时,就会将其添加至父View上                    if (root != null && attachToRoot) {                        root.addView(temp, params);                    }                    //如果父Viewwe为null或者没有绑定父View都会将当前解析的View返回,否则返回父View                    if (root == null || !attachToRoot) {                        result = temp;                    }                }

仔细分析上述代码,可以得出如下结论:

从这段代码中,得出以下几个结论:

  1. 当root为null时,attachToRoot参数无效,而解析出的View作为一个独立的View存在(不存在LayoutParams)。

  2. 当root不为null时,attactToRoot为false,那么会给该View设置一个父View的约束(LayoutParams),然后将其返回。

  3. 当root不为null时,attactToRoot为true,那么该View会被直接addView进父View,然后会将父View返回。

  4. 当root不为null的话,attactToRoot的默认值是true。

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {        return inflate(parser, root, root != null);    }

上面的代码中,我们还少分析了一处代码rInflateChildren(),即解析子类:

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,            boolean finishInflate) throws XmlPullParserException, IOException {        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);    }

可以看到这里面解析子类调用了rInflate方法, 在来一次rInflate()的分析连接。

Android 中LayoutInflater(布局加载器)源码篇之rInflate方法

如果你之前没看过这段代码,其实你会像博主之前一样,一直在试,而不知道这段代码正确的含义,但是有时候源码会是一个很好的老师,通过它能够得到你想要的。


流程图

这里写图片描述

阅读全文
2 0