Android吃透inflate方法(二)

Question Three:「How?」

源码

弄懂了工作的效果,为了知道工作的原理,我们就要看看工作的流程了

接下来我将在代码中用注释方式来解释代码内容

inflate(resource,root,attachToRoot)

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    //利用inflater对象构建的时候的context获取资源
    final Resources res = getContext().getResources();

    //DEBUG模式的日志
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }

    //XML解析器
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        //之所以要try的原因是会抛出转换异常,比如你xml写了矛盾的东西
        //但是try了之后没有catch,所以出了异常还是崩溃
        //try的意义仅仅是为了finally里关闭资源
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

由于XmlResourceParser只是XML解析器,对应生成对象,所以这里不写,有兴趣的可以自己深入去看。

这里我们发现跳转到了一个同名的重载方法inflate(parser, root, attachToRoot),接下来看看这个吧

inflate(parser,root,attachToRoot)

仔细查看源码发现,这是真正的转换方法,所有转换最后都会汇聚到这个方法。在看这个方法前,我们可以先了解一下他的JDoc文档

简单翻译如下:

  1. xml 转换成 view 的过程重度依赖xml在编译期间预处理生成的文档,这样能提高运行时生成View的速度
  2. 出于以上考虑,不能在运行时转换没有预处理的 xml 为 view

如果能直接转换xml的话,也许热更新又多了一条歪路也说不定,23333

源码如下:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    // 锁mConstructorArgs,相同的inflater对象同步进行
    // 因为下面会对这个变量进行修改,不过结束的时候会复原
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        // mContext是LayoutInflater.from(context)的context
        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        // mConstructorArgs[0]临时使用inflaterContext(mContext)的值,
        // 而其原先值临时存到lastContext中
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;

        // 置为root,如果root != null && attachToRoot = true
        // 那么addView()后就直接返回root
        View result = root;

        try {
            // 查找根节点
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // 跳过所有非START_TAG(<...>)和END_DOCUMENT
            }

            // 可能出现的情况有:
            // 1.找到了START_TAG; 
            // 2.没找到START_TAG(遇到END_DOCUMENT结束)
            // 而没有START_TAG意味着xml内容错误,将抛出异常
            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }

            // 解析根布局名,如刚才Q2的测试,这里就会是LinearLayout,View
            final String name = parser.getName();

            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }

            // <merge>标签单独判断
            if (TAG_MERGE.equals(name)) {
                // <merge>只能在root!=null且attachToRoot=true时使用
                // 否则抛异常
                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 {
                // 不是<merge>表明可以作为根元素,命名为temp
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                // 创建了LayoutParams,需要用root来实例化
                ViewGroup.LayoutParams params = null;
                // 判断root是否为空
                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // 用root来构建temp的LayoutParams
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // attachToRoot为false,那就直接设置LayoutParams
                        // 否则之后会执行addView()
                        temp.setLayoutParams(params);
                    }
                }

                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }

                // 等于是把temp作为新的root,attachToRoot设为true
                // 转换剩下来的部分
                rInflateChildren(parser, temp, attrs, true);

                if (DEBUG) {
                    System.out.println("-----> done inflating children");
                }

                // 前面说的,root != null && attachToRoot = true的情况
                // 不会直接直接设置LayoutParams,而是addView()
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // 不需要addView的话,就返回temp
                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;
    }
}

这样我们就对inflate方法有了一个大致了解,也知道我们实验出来的结果的原理了。

那么接下来我们看看inflate方法里调用的另外俩方法吧

rInflate(parser,parent,context,attrs,finishInflate)

想查文档,但是好像是因为是default的方法,官网只有public的方法文档……

不过我在源文件里找到了文档,一起来看看:

/**
 * Recursive method used to descend down the xml hierarchy and instantiate
 * views, instantiate their children, and then call onFinishInflate().
 * <p>
 * <strong>Note:</strong> Default visibility so the BridgeInflater can
 * override it.
 */

简单来说:rInflate的 r 指的是 Recursive 递归xml布局来初始化view,可以想象xml布局嵌套层数一高会造成多大的性能问题……

源码,请:

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;

    // 遍历到遇到END_TAG,或者END_DOCUMENT为止
    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();

        // 如果设置了TAG_REQUEST_FOCUS
        if (TAG_REQUEST_FOCUS.equals(name)) {
            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)) {
            // 子View不能有merge标签,merge只能用在根布局
            throw new InflateException("<merge /> must be the root element");
        } else {
            //通过createViewFromTag构建这个view
            final View view = createViewFromTag(parent, name, context, attrs);
            // 利用parent作为ViewGroup,构建出LayoutParams,并赋值予子View
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            // addView到ViewGroup
            viewGroup.addView(view, params);
        }
    }

    // TAG_REQUEST_FOCUS的处理
    if (pendingRequestFocus) {
        parent.restoreDefaultFocus();
    }

    // finishInflate是方法传参进来的
    if (finishInflate) {
        parent.onFinishInflate();
    }
}

看起来和inflate差不多,就真的只是递归而已,真正生成view的方法应该就是那个createViewFromTag了

View createViewFromTag

这个方法的签名有点长,就不写子标题上了

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr)

最后一个参数可不写,默认为false

同样,这也是一个default的方法,文档在代码里

/**
 * Creates a view from a tag name using the supplied attribute set.
 * <p>
 * <strong>Note:</strong> Default visibility so the BridgeInflater can
 * override it.
 *
 * @param parent the parent view, used to inflate layout params
 * @param name the name of the XML tag used to define the view
 * @param context the inflation context for the view, typically the
 *                {@code parent} or base layout inflater context
 * @param attrs the attribute set for the XML tag used to define the view
 * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
 *                        attribute (if set) for the view being inflated,
 *                        {@code false} otherwise
 */

简单来说,就是根据提供的attribute set,标签名,来构建View

源码如下:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    // 判断ignoreThemeAttr,给context配置Theme
    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();
    }

    // !?这啥,1995?party?
    // 仔细一看发现BlinkLayout还是LayoutInflater的私有内部静态类
    // 试了一下发现是一个ViewGroup,效果是0.5秒闪烁一次
    // 可以可以,这很1995的patry,disco舞厅闪光灯
    // 看源码送彩蛋,可还行233333
    if (name.equals(TAG_1995)) {
        // 下面这个是官方注释↓
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    try {
        View view;
        // 这是一个找factory的过程
        // 找mFactory2,mFactory,mPrivateFactory
        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 {
                // 没有'.'就表示是原生的View,不需要库文件的
                if (-1 == name.indexOf('.')) {
                    // onCreateView其实就调用了
                    // createView(name, prefix:"android.view.", attrs)
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        // 结束,返回
        return view;
    } catch (InflateException e) {
        throw e;

    } catch (ClassNotFoundException e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;

    } catch (Exception e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    }
}

View createView(name,prefix,attrs)

这次有官方文档了

说的是,这个是根据名字实例化View的底层方法,虽然说是public,但是直接调用的时候必须处理 抛出异常 ,返回值为 null 的两种情况

搓手手看源码:

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    // 全局静态HashMap缓存
    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) {
            // 没得缓存,自行反射加载
            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 {
            // 就算已经缓存了,也要通过mFilter进行检查
            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);
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
        }

        Object lastContext = mConstructorArgs[0];
        if (mConstructorArgs[0] == null) {
            // Fill in the context if not already within inflation.
            mConstructorArgs[0] = mContext;
        }
        Object[] args = mConstructorArgs;
        args[1] = attrs;

        // 创建新View实例,args是自定义主题相关的变量
        final View view = constructor.newInstance(args);
        // 如果是ViewStub
        if (view instanceof ViewStub) {
            // 等会用同一个Context加载这个ViewStub的LayoutInflater
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        mConstructorArgs[0] = lastContext;
        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);
    }
}

稳了,总算看到了怎么实例化view的,也不过是用反射,不过还做了两层缓存,一层全局,一层在inflater对象里

总结

inflate方法先把最外层的root弄好,然后用rInflate去递归把子view都弄好,子view用createViewFromTag方法去解析tag,用createView反射出view,全都弄完再返回。

很明显,整个过程中最耗时(ANR)的地方有两处:

  1. 解析XML
  2. 反射获取实例

而Google做的优化是:

  1. 预编译
  2. 缓存

而我们可以做的就是,减少布局层次,降低复杂度。其实这样除了能减少inflate的时间,还能减少measure layout draw的时间,不过不是本期重点,就不细讲了。

猜你喜欢

转载自blog.csdn.net/indeedes/article/details/120609288
今日推荐