关闭

[置顶] Android 中LayoutInflater(布局加载器)之源码篇

标签: android源码阅读博客
435人阅读 评论(0) 收藏 举报
分类:

本文出自博客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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:42142次
    • 积分:1016
    • 等级:
    • 排名:千里之外
    • 原创:36篇
    • 转载:0篇
    • 译文:3篇
    • 评论:117条
    博客专栏