LayoutInflater.inflate函数流程分析(基于10.0.0)

LayoutInflater中inflate函数总共有4个形态
View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 

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

前两个是我们经常用的,且第一个也是直接调用第二个的,那我们来分析一下第二种

/frameworks/base/core/java/android/view/LayoutInflater.java

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }

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

从代码中可以看出,先使用预编译加载(Android10的release版本暂时不支持),如果失败了,再使用inflate的另一个重载的函数来实现功能,下面我们看下tryInflatePrecompiled函数的实现

/frameworks/base/core/java/android/view/LayoutInflater.java

private @Nullable
    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
        // 如果不使用预编译,就直接返回
        boolean attachToRoot) {
        if (!mUseCompiledView) {
            return null;
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");

        // Try to inflate using a precompiled layout.
        // 获取resource资源所在包名
        String pkg = res.getResourcePackageName(resource);
        // 获取resource资源的xml文件的名字
        String layout = res.getResourceEntryName(resource);

        try {
            // 获取预编译好的类,针对每个包资源里面的layout目录都会编译成一个类,layout目录中的每一个xml都会对应成一个方法,方法名就是xml文件的名字
            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
            // 找到对应xml文件名的方法
            Method inflater = clazz.getMethod(layout, Context.class, int.class);
            // 调用找到的方法
            View view = (View) inflater.invoke(null, mContext, resource);
            
            // 根据 parent和 attachToRoot 来设置View的Layout参数
            if (view != null && root != null) {
                // 解析resource对应的xml,获取根对象layout_width和layout_height参数
                XmlResourceParser parser = res.getLayout(resource);
                try {
                    AttributeSet attrs = Xml.asAttributeSet(parser);
                    advanceToRootNode(parser);
                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);

                    if (attachToRoot) {
                        root.addView(view, params);
                    } else {
                        view.setLayoutParams(params);
                    }
                } finally {
                    parser.close();
                }
            }

            return view;
        } catch (Throwable e) {

        } finally {
        }
        return null;
    }

该函数的大体流程是,现获取xml资源所在包的包名,在获取资源对应的xml文件名,然后从mPrecompiledClassLoader中获取类名和包名一致的类对象,获取对象的的方法(方法名是前面获取的xml资源文件名),然后调用该方法生成View,在根据tryInflatePrecompiled函数传入的参数设置View的LayoutParams.

下来我们看一下mPrecompiledClassLoader对象,看它是怎么来的

/frameworks/base/core/java/android/view/LayoutInflater.java

    private void initPrecompiledViews(boolean enablePrecompiledViews) {
        ...
        mPrecompiledClassLoader = mContext.getClassLoader();
        String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME;
        if (new File(dexFile).exists()) {
            mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader);
        } 
        ....
    }

看代码得知mPrecompiledClassLoader是一个PathClassLoader对象,加载一个名为"/compiled_view.dex"的文件,这个文件所在路径是mContext.getCodeCacheDir()函数获取的(这是一个文件系统上的绝对路径,用来缓存代码的,升级或删除app都会清空这个路径下的文件),个人猜测这个compiled_view.dex文件应该是app安装的时候系统生成的,把app资源里面的layout目录下的xml都预先编译成能生成对应View的类,这样每次在inflate一个layout.xml时不用再次解析xml文件,从而加快layout的inflate过程,好了,预加载就先告一段落,下面我们看一下预加载失败的处理流程,XmlResourceParser parser = res.getLayout(resource),然后调用View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 函数,Resource.getLayout(int resourceId)的过程我们在另一篇文章中做分析,下面我们看看View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 函数:

/frameworks/base/core/java/android/view/LayoutInflater.java

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

            final Context inflaterContext = mContext;

            // 获取xml跟对象的属性组
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                advanceToRootNode(parser);
                final String name = parser.getName(); // 获取跟节点的标签名

               

                if (TAG_MERGE.equals(name)) {// 如果是merge标签,merge一般和include,它必须挂载在一个root下,如果传入的参数不合法,就会抛出异常
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    // 解析merge标签
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else { // 正常View节点
                    // Temp is the root view that was found in the xml
                    // 创建XML中根节点对应的View
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    // 设置根节点View的LayoutParams
                    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);
                        }
                    }

                    // 创建根节点的所有子节点
                    rInflateChildren(parser, temp, attrs, true);

                    // 根据传入的参数设置XML根节点的挂载情况
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    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(
                        getParserStateDescription(inflaterContext, attrs)
                        + ": " + 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;
        }
    }

大致流程是,判断XML的跟标签的类型,如果是merge,调用rInflate(parser, root, inflaterContext, attrs, false); 其他则先调用 final View temp = createViewFromTag(root, name, inflaterContext, attrs);创建TopView,然后在调用rInflateChildren(parser, temp, attrs, true);创建TopView的所有跟节点,下面分析下View createViewFromTag(View parent, String name, Context context, AttributeSet attrs)

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        // 如果标签名为view,则去属性的中的class对应值作为带创建View的类名
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        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();
        }

        try {
            // 尝试调用用户自定义的工厂对象来创建View
            View view = tryCreateView(parent, name, context, attrs);
            
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) { // 如果类名中没有.,内置类
                        view = onCreateView(context, parent, name, attrs);
                    } else { // 用户自定义类
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch(Exception e)
        {
        }
    
    }

根据XML标签创建对象时先调用tryCreateView来创建对象

 public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context,
        @NonNull AttributeSet attrs) {
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        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);
        }

        return view;
    }

在tryCreateView中有个特殊处理,就是发现标签是blink是,直接创建一个BlinkLayout后返回,后面尝试用内部Factoty创建对象,这里是Android提供给用户的扩展,我们可以自定义Factory来接管对象创建过程

在调用tryCreateView创建不成功时,进入系统默认的创建流程,如果是XML标签不带“.”则默认是系统内置View,调用onCreateView,否则认为是用户自定义的View,调用createView,实际上onCreateView中是用prefix="android.view"参数调用createView,那么我们看下createView

public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable 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 {
            
            
            if (constructor == null) { // 没找到就创新的类构造器,并添加到缓存中
                // Class not found in the cache, see if it's real, and try to add it
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, 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 = Class.forName(prefix != null ? (prefix + name) : name, false,
                                mContext.getClassLoader()).asSubclass(View.class);

                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, viewContext, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                }
            }

            Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = viewContext;
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            try {
                // 这里是重点,调用构造器并传入属性创建View
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) { // 如果是ViewStub,保存下当前的Context
                    // Use the same context when inflating ViewStub later.
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                return view;
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        } catch (Exception e) {
            ...
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

从上面代码可以看到,createView函数会现在缓存中找View类构造器,没找到就根据name和prefix创建类构造器,然后调用View view = constructor.newInstance(args);来创建View,View创建成功后,针对ViewStub做特殊处理,保存当前的Context到View中,方面后面inflate时用,调用该函数收,就得到了一个View对象

下来我们看下rInflateChildren创建TOPView的自VIew流程

 final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

它内部直接调用了rInflate(parser, parent, parent.getContext(), attrs, finishInflate);,前面我们知道,在标签名为merge的时候调用了rInflate,那么来看看这两次调用有什么不同,merge时rInflate(parser, root, inflaterContext, attrs, false); 创建TopView子View时

rInflateChildren(parser, temp, attrs, true);其实就是Parent和最后的参数不一样,我们看看rInflate的代码
void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        // 循环处理子标签
        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)) { // 处理requestFocus标签
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {    // 处理tag标签
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) { // 处理include标签
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) { // 处理merge标签,merge只能是跟,所以不能出现在这里
                throw new InflateException("<merge /> must be the root element");
            } else { // 这里是真正处理创建View的过程
                // 创建一个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);
                // 添加到父View里面
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

上面过程就是用while循环把当前View的所有直接子VIew都创建出来,然后在递归调用rInflateChildren(parser, view, attrs, true);把子View的子View创建出来,到此inflate就流程就分析完成了

我们总结下inflate流程

1、首先调用tryInflatePreCompile函数通过预编译的方式创建View,不过这个逻辑在10.0.0的release版本中还没有生效,这个过程需要安装APK时配合,预先把layout资源编译成对应的类

2、如果上一步失败了,就调用Resource类的getLayout函数获取layout资源的XmlResourceParser对象,然后调用inflate函数的另一个重载形式:View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)

3、获取跟标签及其属性,判断跟标签是不是"merge",如果是就调用rInflate函数,如果不是调用createViewFromTag函数创建根标签对应的TopView,然后调用rInflaterChild函数创建根标签的所以子标签对应View(这是一个递归过程);

4、根据传入的参数则是TopView的LayoutParams参数或者是直接添加到传入的root对象

5、根据传入的参数决定是返回TopView(root == null || bAttachRoot == false),还是直接返回传入的root(root  != null && bAttachRoot );

6、整个流程结束

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值