Android中的LayoutInflater分析(二)

文章收藏的好句子:好好提升自己,以后人生路上就少一些尴尬,尴尬到来的前几年,其实我们有机会避免的,不要得过且过。

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) 方法看起;

e15ee7aa697badf6c47b42c4303e20b1.png

看注释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) 方法;

a161cb4e5b9e1c83c29665c273de7084.png

看注释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 对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值