LayoutInflater源码最简解读 带你轻松领略源代码之美

来源:互联网 发布:java 对url encode 编辑:程序博客网 时间:2024/06/04 19:30

首先,我们获取LayoutInflater的方式:LayoutInflater.fron(context);是被封装的过的

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;}
其实是通过获取Context.LAYOUT_INFLATER_SERVICE这个系统服务来得到的。


解析xml

LayoutInflater的作用就是加载布局,所以肯定需要解析xml布局。其实如果年头比较老的安卓工作者,都知道后台返回给你的数据不是json格式的,而是xml格式的。所以他们,非常熟悉怎么去解析xml。在这里,解析xml布局也是一样的方法。XmlResourceParser。这一块的知识,在郭霖老师的第一行代码第二版里写的非常详细。当然这个东西不掌握也行,因为现在传数据一般都采取json的格式,而且后面的源码解析xml也是十分通俗易懂。


synchronized:锁住xml解析过程

synchronized (mConstructorArgs) {
这样是为了保证同一时间只有一个线程执行解析操作,把里面共享的资源保证同时只有一个线程使用,保证了线程的安全性,使之不发生错乱。(简单来说,就是封锁住静态资源因为这个方法以外的因素而改变,只要进入这个锁方法,静态资源不会被外界改变)


根布局的解析:
略过中间部分:因为是找到根布局,所以找里面的节点(在解析中,比如LinerLayout,TextView都算一个节点)根本没有必要,只需要找首位节点即可。

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!");}
直接用一个while循环略过中间部分的搜寻,下面的if判断,如果不是开始的节点(即根布局),就报错。


如果根布局是merge:(经常采用merge性能优化的人可能很清楚,这个是用来优化UI速度的标签)

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);}
熟悉的人很清楚,merge作为根布局,必须添加一个父布局而且true(代表愿意设置父布局),否则就会有红红的波浪线提示你错误。rInfalte用来加载子节点(后面会讲)


如果根布局是正常的东西:(merge以外)

调用createViewFromTag方法创建根布局,一会儿讲

final View temp = createViewFromTag(root, name, inflaterContext, attrs);


布局参数:通过LayoutParams类加载根布局参数

ViewGroup.LayoutParams params = null;
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {    // Set the layout params for temp if we are not    // attaching. (If we are, we use addView, below)    temp.setLayoutParams(params);}
如果你不打算把这个布局添加到另外的布局里,你就可以把创建好的参数给这个view,所以我断定这个参数是父的属性


也是一样加载子布局

rInflateChildren(parser, temp, attrs, true);

如果你打算把这个布局添加到另外的布局里,就要把view和参数一起给他,参数算是嫁妆

if (root != null && attachToRoot) {    root.addView(temp, params);}
有人可能会疑惑,addView应该就一个view的参数就可以,为什么还有参数,因为他也被封装了。

(加载父布局基本就讲完了,最后面会贴出所有代码。此外如果读者对照着as里的源码看,效果可能会更好)



createViewFromTag

郭霖说的没错,我去源码里找了一下,这个方法最终还是调用了createView方法,通过反射来创建根布局的实例


什么是反射?为什么要用反射?

因为上面那个加载根布局的方法,他哪里知道你的根布局是哪个类?(熟悉view的人知道,布局都是自定义或者安卓帮你定义的类,也就是对象)安卓总不能一个用一个switch case帮你识别各种view应该创建哪个类吧。那我们又应该怎么创建这个类的实例?所以就用到了反射技术。反射技术是这样的一种技术:通俗来讲,再编译时,java会获取这个未知类的真实类型,继而创建这个类的实例。(关于这个类一点都不知道也不行,类名还是要知道的,parser(xml解析器)就是一个盛放这个类名的容器(他在找解析xml的时候偷偷地把根布局的类名放到了口袋里,装作若无其事的样子),然后传递。)


(前方高能,可以跳过)

反射:接下来讲一下通过反射创建这个实例的过程(反射技术强大,但是真的不难,可以看看我的有关反射的博客,写的十分通俗)

类加载器classloader:

if (constructor != null && !verifyClassLoader(constructor)) {    constructor = null;    sConstructorMap.remove(name);}

先通过verifyClassLoader鉴别一下,这个方法判断classloader是不是LayoutInflater那一派(双亲委托模型)的,如果是,直接用,就不用另外创建了,大大加快了效率,如果不是,只能忍痛设为空了。


获取类名:

prefix != null ? (prefix + name) : name
如果你前面的包名不为空,类名就是 包名+类名,如果没有包名,那就是直接是类名。(追溯下来发现这里传入的包名是空,因为这是安卓内部的,然而你要自定义控件, 这个包名就肯定不能为空了)


生成实例:

Class<? extends View> clazz = null;

clazz = mContext.getClassLoader().loadClass(        prefix != null ? (prefix + name) : name).asSubclass(View.class);
后面的asSubclass就是等于告诉编译器这个类是View的子类。可能有人认为这是多此一举,但是你不这么做,你就会被定义成一个Object类,而你上面说明的是View的子类,那么这里从Object到View就会发生强制类型转换,发生的结果就是可能会有bug或者这个类来自View的属性会在转型的过程中丢失,当然最大的可能性是编译的时候都通过不了,或者红红的波浪线从一开始就提示你不能这么做。


LayoutInflater内部接口Filter

if (mFilter != null && clazz != null) {    boolean allowed = mFilter.onLoadClass(clazz);    if (!allowed) {        failNotAllowed(name, prefix, attrs);    }}
这里mFilter肯定在另外地方有实现,用来对这个类进行一定的规范,比如安卓自定义控件,有些地方不符合规范,在这个地方就很有可能被拦截下来。


存储构造器:(如果没有这个系列的构造器的话)

constructor = clazz.getConstructor(mConstructorSignature);constructor.setAccessible(true);sConstructorMap.put(name, constructor);

Filter存储,方便下次使用(请注意:性能优化的实例,可以借鉴):

如果Filter不为空,直接用(为什么上面不用,因为上面是没有构造器的情况,而Filter是依赖于构造器的)

Boolean allowedState = mFilterMap.get(name);
else if (allowedState.equals(Boolean.FALSE)) {    failNotAllowed(name, prefix, attrs);}

在最后,还要判断这个布局是不是ViewStub的子类(如果对性能优化有了解的朋友,应该对ViewStub很明白)

if (view instanceof ViewStub) {    // Use the same context when inflating ViewStub later.    final ViewStub viewStub = (ViewStub) view;    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));}
强转一波


反射终于谈完了,现在讲正题。


rInflate:加载子布局

加载子布局就很有趣了,因为view是树形结构,所以这里是通过递归,不断遍历加载的,涉及到了算法,也很值得一讲!


当前树的深度:他会不断往里面探索,而parser.getDepth()也会不断的在变。这里在最上面声明了初始深度,方便后面进行比较。

final int depth = parser.getDepth();


判断从根节点开始:在刚进入循环的时候进行判定当前是不是初始节点,如果不是,continue,本次循环结束,直到找到初始节点为止

if (type != XmlPullParser.START_TAG) {    continue;}

进行优雅的递归

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);viewGroup.addView(view, params);
一看,这里是rInflateChildren,哪里是递归啊但是实际上,这个方法内部又调用了rInflater

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,        boolean finishInflate) throws XmlPullParserException, IOException {    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);}
为什么要这样做?有些人会认为多此一举,我个人想法,如果就自身调用自身的话,你会创造出许多许多个rInfalter方法来,虽然你看不见,但是资源消耗过大。可以看看这里,如果递归的话就是两个方法之间跳来跳去,资源无疑会节省许多。所以我认为,这是一种优雅的递归,用于处理大而负责的递归情况,不然数据一大,调用自身的那种递归会报StackOverFlow(堆栈溢出)的错。


这就基本讲完了。

其实源码并不可怕,虽然我们今天遇到许多生疏的知识点:反射,xml解析,等等。这些都是值得学习的方向,你应该感到快乐,应该有一种遇到难题的兴奋(这种兴奋多去刷刷hdu的acm算法题会培养出来,嘿嘿(学安卓的人,等你学了算法和数据结构之后,你会发现,你比以前自信了一百倍,耐心了一千倍!))

下面贴源码,不过你们as里也有。

inflate

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 (DEBUG) {                System.out.println("**************************");                System.out.println("Creating root view: "                        + name);                System.out.println("**************************");            }            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 {                // Temp is the root view that was found in the xml                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                    params = root.generateLayoutParams(attrs);                    if (!attachToRoot) {                        // Set the layout params for temp if we are not                        // attaching. (If we are, we use addView, below)                        temp.setLayoutParams(params);                    }                }                if (DEBUG) {                    System.out.println("-----> start inflating children");                }                // Inflate all children under temp against its context.                rInflateChildren(parser, temp, attrs, true);                if (DEBUG) {                    System.out.println("-----> done inflating children");                }                // We are supposed to attach all the views we found (int temp)                // to root. Do that now.                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.                if (root == null || !attachToRoot) {                    result = temp;                }            }        } catch (XmlPullParserException e) {            final InflateException ie = new InflateException(e.getMessage(), e);            ie.setStackTrace(EMPTY_STACK_TRACE);            throw ie;        } catch (Exception e) {            final InflateException ie = new InflateException(parser.getPositionDescription()                    + ": " + e.getMessage(), e);            ie.setStackTrace(EMPTY_STACK_TRACE);            throw ie;        } finally {            // Don't retain static reference on context.            mConstructorArgs[0] = lastContext;            mConstructorArgs[1] = null;            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }        return result;    }}
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);                }            }            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[] args = mConstructorArgs;        args[1] = attrs;        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]));        }        return view;    } catch (NoSuchMethodException e) {        final InflateException ie = new InflateException(attrs.getPositionDescription()                + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);        ie.setStackTrace(EMPTY_STACK_TRACE);        throw ie;    } catch (ClassCastException e) {        // If loaded class is not a View subclass        final InflateException ie = new InflateException(attrs.getPositionDescription()                + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);        ie.setStackTrace(EMPTY_STACK_TRACE);        throw ie;    } catch (ClassNotFoundException e) {        // If loadClass fails, we should propagate the exception.        throw e;    } catch (Exception e) {        final InflateException ie = new InflateException(                attrs.getPositionDescription() + ": Error inflating class "                        + (clazz == null ? "<unknown>" : clazz.getName()), e);        ie.setStackTrace(EMPTY_STACK_TRACE);        throw ie;    } finally {        Trace.traceEnd(Trace.TRACE_TAG_VIEW);    }}
rinflate

void rInflate(XmlPullParser parser, View parent, Context context,        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {    final int depth = parser.getDepth();    int type;    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)) {            parseRequestFocus(parser, parent);        } else if (TAG_TAG.equals(name)) {            parseViewTag(parser, parent, attrs);        } else if (TAG_INCLUDE.equals(name)) {            if (parser.getDepth() == 0) {                throw new InflateException("<include /> cannot be the root element");            }            parseInclude(parser, context, parent, attrs);        } else if (TAG_MERGE.equals(name)) {            throw new InflateException("<merge /> must be the root element");        } else {            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);            viewGroup.addView(view, params);        }    }    if (finishInflate) {        parent.onFinishInflate();    }}



最后面提出一个问题:

如果某个控件在xml中被设置为setVisblity = gone,那么这个情况他什么时候会被发现并且被略过,不加载?


我找遍了源码,发现都没有。那么可以推断出来,他是在xml布局被提交上去的时候就被过滤了。