深入分析 LayoutInflater

从一个实际例子开始

通常在 Activity 的 onCreate 里,我们都会调用 setContentView 方法,看下此方法

    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

这里调用了 Window 的 setContentView,Window 是一个抽象类,我们知道其具体实现是 PhoneWindow

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;

    @Override
    public void setContentView(int layoutResID) {
        ...
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ...
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...
    }

这里,首先,当判断 mContentParent 是空的时候,先初始化 DecorView 并将其包裹在 mContentParent 中,

之后用 inflate 方法把指定布局的视图加载到 mContentParent 中。

解析 inflate 方法

   public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

这里把 root 是否为空当作第三个参数传入下个方法

   public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

这个方法先是获取了 xml 资源解析器,接着把资源解析器,root,root是否为 null 三个参数传入下个方法

    /**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * {@link InflateException} if there is an error.
     * <p>
     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
     * reasons, view inflation relies heavily on pre-processing of XML files
     * that is done at build time. Therefore, it is not currently possible to
     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
     *
     * @param parser XML dom node containing the description of the view
     *        hierarchy.
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        <em>attachToRoot</em> is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if <em>attachToRoot</em> is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            //Context 对象
            mConstructorArgs[0] = inflaterContext;
            //存储父视图
            View result = root;
            try {
                int type;
                //找到 root 元素
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                }
                ...
                final String name = parser.getName();
                ...
                //解析 merge 标签
                if (TAG_MERGE.equals(name)) {
                    ...
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    //不是 merge 标签就解析布局中的视图,name 是要解析视图的类名
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        ...
                        //生成布局参数
                        params = root.generateLayoutParams(attrs);
                        //如果 attachRoot 是 false,就给 temp 设置布局参数
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    ...
                    //解析 temp 视图下所有子 View
                    rInflateChildren(parser, temp, attrs, true);
                    ...
                    //如果 root 不为空且 attachToRoot 为 true,就把 temp 添加到父视图
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    //如果 root 为空或 attachToRoot 为 false,就返回 temp
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            } catch (XmlPullParserException e) {
                ...
            } finally {
                ...
            }
            return result;
        }
    }

此方法是 inflate 的终极实现,主要进行了如下几步工作:

  1. 解析 xml 根标签。
  2. 如果根标签是 merge 就调用 rInflate 解析,其会把 merge 标签下所有子 View 添加到根标签下。
  3. 如果根标签是普通元素就调用 createViewFromTag 对该元素进行解析。
  4. 调用 rInflate 解析 temp 根元素下所有子 View,且将这些 View 添加到 temp下。
  5. 返回解析到的根视图。

探索 createViewFromTag 方法

我们先着手看 createViewFromTag 方法

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }

传入了第五个参数到同名方法

 /**
     * 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
     */
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        ...
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }
        try {
            View view;
            ...
            if (view == null) {
                ...
                try {
                    //内置 View 控件的解析
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        //自定义控件的解析
                        view = createView(name, null, attrs);
                    }
                } finally {
                    ...
                }
            }
            return view;
        } catch (InflateException e) {
            ...
        }
    }

这段程序中有段判断,当控件的名字不.时说明是内置控件,含.时说明是自定义控件。

那么 onCreateView 方法是在何处调用的呢?我们等下再去考虑。

到此为止,我们完成了解析单个 View 的过程,而整个窗口是一棵视图树,最终完成的方法在 rInflate

深入 rInflate

    /**
     * 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.
     */
    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)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                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 {
                //根据元素名解析
                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 添加到 parent 中
                viewGroup.addView(view, params);
            }
        }
        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }
        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

此方法通过深度优先遍历构造视图树,每解析到一个 View 都会递归,直到路径下的最后一个元素,

之后回溯将每个 View 添加到 parent 中。此方法解析完以后,整个视图树构造完毕,调用 onResume 以后,

通过 setContentView 设置的内容会出现在视野里。

找到 onCreateView 方法真正实现

LayoutInflater 是个抽象类

public abstract class LayoutInflater {...}

没关系,既然是抽象类,那就总能找到它的实现类,

根据我的上一篇文章 getSystemService 追根溯源(基于最新源码)中的分析,

最终获取 LayoutInflater,加载 SystemServiceRegistry 类时会通过如下代码把 LayoutInflater 注入容器

static{
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
}

这里我们发现真正 LayoutInflater 的实现就是 PhoneLayoutInflater,看下这个类

public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
    ...
    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                ...
            }
        }
        return super.onCreateView(name, attrs);
    }
    ...
}

其中主要重写了 onCreateView 方法,

此方法就是在传递进来的 view 的名字前面加一个前缀用来得到该内置类的完整路径,然后创建相应对象。

再看一下 LayoutInflater 中 onCreateView 的实现

    /**
     * This routine is responsible for creating the correct subclass of View
     * given the xml element name. Override it to handle custom view objects. If
     * you override this in your subclass be sure to call through to
     * super.onCreateView(name) for names you do not recognize.
     *
     * @param name The fully qualified class name of the View to be create.
     * @param attrs An AttributeSet of attributes to apply to the View.
     *
     * @return View The View created.
     */
    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }

结合上文的分析,我们发现自定义控件和内置控件最终都是调用 createView,研究这个方法很有必要

分析 createView 方法

    /**
     *根据完整的类名通过反射机制构造 View 对象
     */
    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) {
                //prefix 不为空就构造完整 View 路径并加载此类
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                ...
                //从 Class 对象中获取构造函数
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                //将构造函数存入缓存中
                sConstructorMap.put(name, constructor);
            } else {
                ...
            }
            Object lastContext = mConstructorArgs[0];
            ...
            Object[] args = mConstructorArgs;
            args[1] = attrs;
            //通过反射构造 View
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            ...
            return view;
        } catch (NoSuchMethodException e) {
            ...
        } finally {
            ...
        }
    }

此方法主要步骤就是获取此类的构造函数并且缓存起来,然后通过构造函数创建此 View 的对象,最后返回。

这就是解析整个 View 的过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值