关于android:Android中的LayoutInflater分析二

PS:本文系转载文章,浏览原文可读性会更好,文章开端有原文链接

ps:本篇文章是基于 Android Api 26 来剖析的

目录

1、LayoutInflater 创立 View 过程

 1、1 LayoutInflater 的 rInflate(该办法有5个参数) 办法剖析

 1、2 LayoutInflater 的 parseInclude(该办法有4个参数) 办法剖析

 1、3 LayoutInflater 的 createViewFromTag 办法剖析


1、LayoutInflater 创立 View 过程

1、1 LayoutInflater 的 rInflate(该办法有5个参数) 办法剖析

咱们接着Android中的LayoutInflater剖析(一)这篇文章持续剖析,咱们看回 LayoutInflater 的 inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) ;

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {

    synchronized (mConstructorArgs) {
        ......
        try {
            ......
            if (TAG_MERGE.equals(name)) {
                ......
                //18、
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                //19、
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ......
                // Inflate all children under temp against its context.
                //23、
                rInflateChildren(parser, temp, attrs, true);
                ......
            }

        } catch (XmlPullParserException e) {
            ......
        } catch (Exception e) {
            ......
        } finally {
            ......
        }

        return result;
    }

}

还记得前一篇文章Android中的LayoutInflater剖析(一)所说的吗?正文18 的代码和正文23 的代码实质上都是解析子标签,只是参数不一样而已,都是调用 LayoutInflater 的 rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) 办法,看正文18 办法的第二个参数 root 和 正文23 办法的第二个参数 temp 是不一样的,当要解析的 xml 文件的开始标签是 merge 时,根标签就是 root,当要解析的 xml 文件的开始标签不是 merge 时,根标签就是 temp;看正文18 办法的第五个参数 root 和 正文23 办法的第四个参数 temp 是不一样的,为 true 时,示意要回调父元素(父元素是 ViewGroup 或者继承于 ViewGroup)的 onFinishInflate 办法,root 作为参数之一,在 LayoutInflater 的 inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 办法被调用之前早就创立好了,所以 root 的 onFinishInflate 办法早就被调用过了,所以正文18 的办法的第五个参数为 false,temp 是在 LayoutInflater 的 inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 办法调用的过程中创立的,才刚创立好 temp,所以 temp 的 onFinishInflate 办法还没被调用过,所以正文23 办法的第四个参数为 true。

咱们看一下 LayoutInflater 的 rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) 办法;

void rInflate(XmlPullParser parser, View parent, Context context,

              AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    ......
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
        ......
        if (TAG_REQUEST_FOCUS.equals(name)) {
            ......
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
            
            //30、
        } else if (TAG_INCLUDE.equals(name)) {
            
            //31、
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            
            //32、
            parseInclude(parser, context, parent, attrs);
            
            //33、
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            ......
        }
    }
    ......
    //34、
    if (finishInflate) {
        parent.onFinishInflate();
    }

}

看正文30 的代码,判断解析的标签是是否 include;正文31 的代码,判断解析的以后标签的深度是否为0,也就是以后标签是否有父标签,如果深度为0示意没有父标签,那么就会抛出异样,因为 include 标签不能够作为一个 xml 文件的根标签,这个大家都晓得的;正文32 的代码是具体解析 include 标签的办法,前面再说;咱们晓得 LayoutInflater 的 rInflate(XmlPullParser parser, View parent, Context context,At-tributeSet attrs, boolean finishInflate) 办法是解析子标签用的,看正文33 的代码,如果子标签为 merge,那么就抛出异样;看正文34 的代码,finishInflate 为 true,就会调用 ViewGroup(只有是父标签,那么它肯定是 ViewGroup,这个不必狐疑的) 的 onFinishInflate 办法,这篇文章的下面有说的。

1、2 LayoutInflater 的 parseInclude(该办法有4个参数) 办法剖析

咱们看回正文32 的代码,也就是 LayoutInflater 的 parseInclude(XmlPullParser parser, Context context, View parent,AttributeSet attrs) 办法;

private void parseInclude(XmlPullParser parser, Context context, View parent,

                          AttributeSet attrs) throws XmlPullParserException, IOException {
    int type;

    if (parent instanceof ViewGroup) {
        // Apply a theme wrapper, if requested. This is sort of a weird
        // edge case, since developers think the <include> overwrites
        // values in the AttributeSet of the included View. So, if the
        // included View has a theme attribute, we'll need to ignore it.
        //35、
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        ......
        // If the layout is pointing to a theme attribute, we have to
        // massage the value to get a resource identifier out of it.
        //36、
        int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
        ......
        //37、
        if (layout == 0) {
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            throw new InflateException("You must specify a valid layout "
                    + "reference. The layout ID " + value + " is not valid.");
        } else {
            
            //38、
            final XmlResourceParser childParser = context.getResources().getLayout(layout);

            try {
                ......
                //39、
                if (TAG_MERGE.equals(childName)) {
                    // The <merge> tag doesn't support android:theme, so
                    // nothing special to do here.
                    rInflate(childParser, parent, context, childAttrs, false);
                } else {
                    final View view = createViewFromTag(parent, childName,
                            context, childAttrs, hasThemeOverride);
                    ......
                }
            } finally {
                childParser.close();
            }
        }
    } else {
        throw new InflateException("<include /> can only be used inside of a ViewGroup");
    }

    LayoutInflater.consumeChildElements(parser);

}

正文35 的代码示意提取出 include 的 thme 属性,如果设置了 them 属性,那么 include 包裹的 View 设置的 theme 有效;正文36 的代码示意通过 include 标签的 layout 属性获取到一个 子布局 xml 文件的 id;正文37 的代码,如果 layout 的 id 为0,证实没有给 include 标签设置 layout 属性,那么就会抛出异样;正文38 的代码示意通过子布局 xml 的 id 创立一个 XmlResourceParser 对象;看正文39 的代码,如果子布局 xml 文件的根标签是 merge,那么又调用到 LayoutInflater 的 rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) 办法,这样就通过 LayoutInflater 的 rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) 办法进行不停的解析标签。

1、3 LayoutInflater 的 createViewFromTag 办法剖析

好了,下面说了解析标签的过程,这里咱们剖析一下 View 的创立过程,看回正文19 的代码,也就是 LayoutInflater 的 createViewFromTag(View parent, String name, Context context, AttributeSet attrs) 办法看起;

图片

看正文40 的代码,LayoutInflater 的 createViewFromTag(View parent, String name, Context context, AttributeSet attrs) 办法又调用了 LayoutInflater 的 createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) 办法;

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,

                       boolean ignoreThemeAttr) {
    ......
    try {
        View view;
        
        //41、
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
            
            //42、
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        
        //43、
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {
            ......
            try {
                if (-1 == name.indexOf('.')) {
                    
                    //44、
                    view = onCreateView(parent, name, attrs);
                } else {
                    
                    //45、
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        return view;
    } catch (InflateException e) {
      ......
    } catch (ClassNotFoundException e) {
      ......
    } catch (Exception e) {
      ......
    }

}

正文42、43、44、45 的代码我就不剖析了,我剖析的是一个出现界面的类间接继承 AppCompatActivity 而不是间接继承 Activity,所以最终会走正文41 的代码,mFactory2 对象的实现类到底是什么,能够看Android中的AppCompatActivity的移花接木之UI偷换这篇文章,Android中的AppCompatActivity的移花接木之UI偷换拿的是 AppCompatDelegateI-mplV9 来剖析,而咱们用的是 API 26 的来剖析,拿的是 AppCompatDelegateImplN 来剖析,那岂不是不一样吗?其实它们实例化并赋值给 mFactory2 的过程是一样的,AppCompatDelegateImplN 间接继承于 AppCompatDelegateImplV9,而且 AppCompatDelegateImp-lN 也没有重写 onCreateView(View parent, String name, Context context, AttributeSet attrs) 办法,onCreateView(View parent, String name, Context context, AttributeSet attrs) 办法依然在 AppCompatDelegateI-mplV9 中实现;留神这里我剖析的是用 Activity 为上下文创立 View 的思路,别搞错了,为了避免搞混同,我列举一下用 Activity 为上下文创立 View 的代码;

//46、
View view = LayoutInflater.from(activity).inflate(R.layout.item_1,null,false);

正文46 中的 LayoutInflater.from(activity) 代码最终会调用到 Activity 的 PhoneLayoutInflater 对象中的 cloneInContext(Context newContext)办法,能够看Android中的LayoutInflater剖析(一)这篇文章追踪一下,而后通过 Activity 的 PhoneLayoutInflater 对象的 cloneInContext(Co-ntext newContext) 办法构建一个新的 PhoneLayoutInflater 对象并将 Activity 的 PhoneLayoutInflater 对象中的 mFactory2 赋值给新的 PhoneLayoutInflater 中的 mFactory2。

咱们看回正文41 if 语句下的代码,也就是 AppCompatDelegateImplV9 的 onCreateView(View parent, String name, Context context, AttributeSet attrs) 办法;

图片

看正文47 的代码,AppCompatDelegateImplV9 的 onCreateView(Vie-w parent, String name, Context context, AttributeSet attrs) 办法又调用 AppCompatDelegateImplV9 的 createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs) 办法;

@Override
public View createView(View parent, final String name, @NonNull Context context,
                       @NonNull AttributeSet attrs) {
    ......
    //48、
    return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
            IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
            true, /* Read read app:theme as a fallback at all times for legacy reasons */
            VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
    );
}

看正文48 的代码,mAppCompatViewInflater 是 AppCompatViewInfla-ter 类型的对象,这里又调用 AppCompatViewInflater 的 createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs, boolean inheritContext,boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) 办法;

public final View createView(View parent, final String name, @NonNull Context context,

                             @NonNull AttributeSet attrs, boolean inheritContext,
                             boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    ......
    View view = null;

    // We need to 'inject' our tint aware Views in place of the standard framework versions
    //49、
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        ......
    }

    if (view == null && originalContext != context) {
        // If the original context does not equal our themed context, then we need to manually
        // inflate it using the name so that android:theme takes effect.
        //50、
        view = createViewFromTag(context, name, attrs);
    }
    ......
    return view;

}

看正文49 的代码,如果是零碎的标签,那么间接通过 new 关键字创立一个 View 对象;如果不是零碎标签,那么就走正文50 的代码,也就是 AppCo-mpatViewInflater 的 createViewFromTag(Context context, String name, AttributeSet attrs) 办法;

private View createViewFromTag(Context context, String name, AttributeSet attrs) {

    ......
    try {
        ......
        if (-1 == name.indexOf('.')) {
            for (int i = 0; i < sClassPrefixList.length; i++) {
                
                //51、
                final View view = createView(context, name, sClassPrefixList[i]);
                if (view != null) {
                    return view;
                }
            }
            return null;
        } else {
            
            //52、
            return createView(context, name, null);
        }
    } catch (Exception e) {
        ......
    } finally {
        ......
    }

}

正文52 的代码通过自定义的标签创立 View,第3个参数 null 示意标签不须要放前缀,因为自定义的标签它本来就带好了前缀,所以不须要再增加;正文51 的代码示意通过零碎标签创立 View,写 xml 布局时零碎标签时没有前缀,所以这里创立 View 之前要增加标签前缀,咱们看看个别罕用的零碎标签的前缀汇合 sClassPrefixList 寄存的数据;

private static final String[] sClassPrefixList = {

        "android.widget.",
        "android.view.",
        "android.webkit."

};

咱们个别罕用的零碎标签放在 widget、view 和 webkit 这3个包上面;咱们看正文51、52 的代码,也就是 AppCompatViewInflater 的 createView(C-ontext context, String name, String prefix) 办法;

private View createView(Context context, String name, String prefix)

        throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);

    try {
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            Class<? extends View> clazz = context.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);

            constructor = clazz.getConstructor(sConstructorSignature);
            sConstructorMap.put(name, constructor);
        }
        constructor.setAccessible(true);
        return constructor.newInstance(mConstructorArgs);
    } catch (Exception e) {
        // We do not want to catch these, lets return null and let the actual LayoutInflater
        // try
        return null;
    }

}

AppCompatViewInflater 的 createView(Context context, String name, String prefix) 办法最终通过反射机制创立一个 View 对象;如果在 xml 布局文件中写入一个不存在的标签(不是零碎标签,也不是自定义标签),那么就会返回一个空的 View 对象。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理