美文网首页Android技术知识Android知识Android开发
安卓 - 源码 - LayoutInflater(二)

安卓 - 源码 - LayoutInflater(二)

作者: 七零八落问号 | 来源:发表于2017-05-04 18:09 被阅读105次

    `上一节:安卓 - 源码分析 - LayoutInflater(一)

    inflate方法的重载:

    inflate方法有很多重载,而最终将会进入:

    /*
     * 3个参数分别为:
     * parser       : 已经包含xml布局的xml解析器
     * root         : 可选参数,生成的布局的根视图,作用取决于attachToRoot参数
     * attachToRoot : 当为true时,生成的View作为子控件添加到根视图root;
                      当为false时,root仅为生成的View提供外部的LayoutParams;
     */
    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
    

    当调用参数为xml资源id的重载方法时:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
    

    则会使用getResources().getLayout(resource)将xml布局资源转换为XmlResourceParser:

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
    

    默认的attachToRoot由root参数是否null决定:

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

    解析过程,只需要关注最终使用的inflate方法。去掉Debug和Trace,取出关键的源码先大概了解:

    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];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
    
            try {
                int type;
    
                //Look for the root node.
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                }
                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 {
                    // 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) {
                        // 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);
                        }
                    }
    
                    // Inflate all children under temp against its context.
                    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.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 (Exception e) {
                ...
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }
            return result;
        }
    }
    

    应该已经很明显了,然后我们一步步分析:

    /*
     * 这里主要做了一些关键对象的配置和临时保存。
     */
    final Context inflaterContext = mContext;
    final AttributeSet attrs = Xml.asAttributeSet(parser);
    Context lastContext = (Context) mConstructorArgs[0];
    mConstructorArgs[0] = inflaterContext;
    View result = root;
    
    /*
     * 这里主要检索xml是否能够被解析。
     * 查找第一个标签,直到文件结束。
     * 一个标签都找不到时,抛出异常,结束解析。
     */
    while ((type = parser.next()) != XmlPullParser.START_TAG &&
        type != XmlPullParser.END_DOCUMENT) {
    }
    if (type != XmlPullParser.START_TAG) {
        throw new InflateException(parser.getPositionDescription()
                + ": No start tag found!");
    }
    
    /*
     * 这里主要是为xml布局文件创建一个根节点,
     */
    if (TAG_MERGE.equals(name)) {
    
        /*
         * 当标签是merge时,必须有父布局及需要添加到父布局,否则异常
         */
        if (root == null || !attachToRoot) {
            throw new InflateException("<merge /> can be used only with a valid "
                + "ViewGroup root and attachToRoot=true");
        }
    
        /*
         * 当第一个标签是<merge/>时,调用rInflate方法解析xml并填充到参数给出的root
         * rInflate方法后面介绍
         */
        rInflate(parser, root, inflaterContext, attrs, false);
    } else {
    
        /*
         * 当第一个标签不是merge时,使用这个标签创建一个根节点temp,
         * 调用createViewFromTag方法创建View,这个方法后面介绍
         */
        final String name = parser.getName();
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
        /* 这里的流程下面介绍 */
        ...
    }
    
    /*
     * 根据root和attachToRoot,为根节点temp配置合适的LayoutParams。
     */
    ViewGroup.LayoutParams params = null;
    if (root != null) {
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            temp.setLayoutParams(params);
        }
    }
    
    /*
     * 通过rInflateChildren方法,将剩余的子节点解析并填充到根节点temp
     * rInflateChildren方法后面介绍
     */
     rInflateChildren(parser, temp, attrs, true);
    
    /* 
     * 这时根节点temp已经解析完毕,并已正确添设置LayoutParams,
     * 如果参数root非空,且参数attachToRoot为true,则将temp添加到root上
     */
    if (root != null && attachToRoot) {
        root.addView(temp, params);
    }
    
    /* 
     * 在开始的时候,result被设置为root,
     * 当root为空,或者attachToRoot为false,即temp没有被添加到root时
     * 应该将temp作为结果返回,即把result设置为temp
     */
    if (root == null || !attachToRoot) {
        result = temp;
    }
    
    /*  清空context引用 */
    mConstructorArgs[0] = lastContext;
    mConstructorArgs[1] = null;
    
    /* 返回结果 */
    return result;
    
    inflate过程调用的几个重要方法:

    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的当前层,用于后面的层次校验,
     * 保证while循环于当前节点内,例如:
     * 当前层为1,当循环到层1时,表示当前节点的子节点全部解析完成
     */
    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;
        }
    
        /* 这里的流程下面介绍 */
        ...
    }
    
    /* 
     * 根据不同的标签进行不同的处理,主要针对几个特殊标签:
     * <requestFocus/>:使用在非容器View内,能使View获取焦点
     * <tag/>         :能在xml进行Tag设置,相当于setTag(id, value)
     * <include/>     :include只能作为子节点使用
     * <merge/>       :merge只能作为根节点使用
     * 具体的parse相对简单,可以自行了解
     * 而不是这几个特殊标签时,进入正常解析流程
     */
    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 {
    
        /*
         * 正常解析流程,看得出这个流程使用了递归算法
         * 调用createViewFromTag,生成当前标签对应的View对象,
         * 然后通过其父容器得到LayoutParams,
         * 然后调用rInflateChildren对该节点下的子节点进行解析填充,
         * 最后,将这个View添加到父容器中。
         */
        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方法:

    /*
     * 不做过多解析,看得出,rInflateChildren只是对rInflate的简单封装
     */
    final void rInflateChildren(XmlPullParser parser, View parent, 
                        AttributeSet attrs,boolean finishInflate)
                        throws XmlPullParserException, IOException {
    
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
    

    createViewFromTag方法:

    View createViewFromTag(View parent, String name, Context context,
                           AttributeSet attrs, boolean ignoreThemeAttr) {
    
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
    
        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)) {
            return new BlinkLayout(context, attrs);
        }
    
        try {
            View view;
            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);
            }
    
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
        } catch (Exception e) {
            ...
        }
    }
    

    这里就涉及到分析一提及到的Factory了,继续慢慢分析:

    /*
     * 对于<view/>标签,取出其class属性替代标签名称
     */
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }
    
    // 这里会产生一个问题,什么是view标签,class属性又是什么
    // 其实view标签是为内部类View使用的,例如:
    //   在com.example.A.class里,有一个自定义View:ViewB.class,
    //   在编译后,ViewB类名则变成了com.example.A.class$ViewB,
    //   而<com.example.A$ViewB/>标签是不能定义的,所以就要使用到<view/>标签
    //   即<view class="com.example.A$ViewB"/>
    // 当然你也可以对普通的View使用同样的定义方法如<view class="Button"/>
    // 注意,使用<view/>标签必须定义class,否则会抛空指针异常
    
    /*
     * 处理主题相关
     */
    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();
    }
    
    /*
     * 这个TAG_1995其实就是<blink/>标签
     * 会为这个标签创建一个BlinkLayout直接返回
     * 这个BlinkLayout,会对内部的控件提供一种闪烁的功能
     * 然而是没什么卵用的,其实就是内部维护了一个handler,
     * 每0.5秒进行一次更新,根据mBlinkState去选择性绘制子控件
     * 这个0.5秒是个final值,不能改变
     */
    if (name.equals(TAG_1995)) {
        return new BlinkLayout(context, attrs);
    }
    
    /*
     * 这里就是View的创建位置,调用优先级为:
     * 自定义Factory > 系统Factory > LayoutInflater默认
     */
    try {
        View view;
    
        if (mFactory2 != null) {
    
            /*
             * 优先使用Factory2
             */
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            
            /*
             * 然后再是Factory
             */
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
    
        /* 这里的流程下面介绍 */
        ...
    }
    
    /*
     * 没有设置自定义Factory时,尝试使用系统添加的Factory进行创建
     */
    if (view == null && mPrivateFactory != null) {
        view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    }
    
    /*
     * 没有设置任何工厂时,使用默认的createView方法创建View
     */
    if (view == null) {
    
       /*
        * 这里也是临时记录context
        */
       final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
    
        try {
            
            /*
             * 这里其实就是看你的view有没有完整的类名
             * 类似Button,那么就是不完整,使用onCreateView
             * 类似com.example.ViewA这样的,就可以直接使用createView
             */
            if (-1 == name.indexOf('.')) {
                view = onCreateView(parent, name, attrs);
            } else {
                view = createView(name, null, attrs);
            }
        } finally {
    
            /*
             * 还原临时记录
             */
            mConstructorArgs[0] = lastContext;
        }
    }
    
    // 返回View
    return view;
    

    onCreateView方法:

    /*
     * 这个方法就是对系统提供的控件补完类名"android.view."
     * 实际上工作的还是createView方法
     */
    protected View onCreateView(String name, AttributeSet attrs)
                    throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }
    

    createView方法:
    还是先贴源码,在这个createView里面,涉及到我们之前说的Filter了,直接解析流程

    public final View createView(String name, String prefix, AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
      
        /*
         * 尝试从构造器缓存取出构造器
         */
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;
    
        try {
            if (constructor == null) {
    
                /*
                 * onCreateView方法的"android.view."在这里拼接
                 * prefix + name
                 */
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                
                /*
                 * 这里就是Filter作出过滤的地方了
                 * 不通过过滤就会通过failNotAllowed排除异常
                 */
                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 {
    
                /*
                 * 有构造器缓存的话,直接使用Filter做一次过滤
                 * 这里做了个优化,缓存了Filter的过滤结果
                 */
                if (mFilter != null) {
                    // 查找Filter缓存
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
                            
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        // 插入Filter缓存
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }
    
            /*
             * 到了这里就知道mConstructorArgs对象里面存放的是什么
             * 分别是context和attrs,老铁,没毛病
             */
            Object[] args = mConstructorArgs;
            args[1] = attrs;
    
            /*
             * newInstance了,反射,没毛病
             */
            final View view = constructor.newInstance(args);
    
            /*
             * 像是ViewStub这样的情况
             * 则将自己(LayoutInflater)复制一份给ViewStub
             * 延迟加载
             */
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            
            /*
             * 到这里终于结束这个创建流程了
             */
            return view;
        } catch (NoSuchMethodException e) {
            ...
        }
    }
    

    上面整个流程涉及到的几个点需要说一下:

    Filter和Factory:
    因为Filter的调用是在LayoutInflater的默认创建过程才会调用的,如果拦截的View在Factory中被返回,则Filter是不会起效的,你也可以在Factory中自行调用Filter进行拦截判断。

    创建子节点和本节点:
    所有的创建本节点,都是使用createViewFromTag方法,
    某一个节点的子节点创建,都是使用了rInflateChildren方法。

    相关文章

      网友评论

        本文标题:安卓 - 源码 - LayoutInflater(二)

        本文链接:https://www.haomeiwen.com/subject/wqartxtx.html