Android setContentView 源码浅析(上)

前言

看源码是一个枯燥的过程,为了达到效果,我们需要带着问题,有目的地去看源码


问题

我们经常使用的Activity的onCreate方法里setContentView(R.layout.activity_main)是如何加载我们的布局文件的?


源码分析

  1. 找到Activity类的setContentView方法:
public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

第一行代码调用了getWindow()方法来获取Window类的对象,然后调用了Window类的setContentView方法

  1. 我们找到Window类:
/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
    /** Flag for the "options panel" feature.  This is enabled by default. */
    public static final int FEATURE_OPTIONS_PANEL = 0;
    ...
    ...
    ...
    public abstract void setContentView(@LayoutRes int layoutResID);
    ...
    ...
    ...
}

这里Window是个抽象类,而我们从注释The only existing implementation of this abstract class is android.view.PhoneWindow 唯一的实现类是PhoneWindow,也就是实际调用的是PhoneWindow

  1. 我们找到PhoneWindow类的setContentView方法:
public void setContentView(int layoutResID) {
        ...
        ...
        ...
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...
        ...
        ...
    }

这里我们就只分析核心代码了,其他的先不看,这里可以看到如果没有转场动画,就调用mLayoutInflater.inflate(layoutResID, mContentParent),我们这里先把传入的mContentParent看作是一个布局父控件

  1. 转到LayoutInflaterinflate(int resource,ViewGroup root)方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

这里调用了inflate(resource, root, root != null)

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

看到XmlResourceParser应该不会感到陌生了吧,接下来看调用inflate(parser, root, attachToRoot)如何解析xml布局文件

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

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();
                
                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                if (TAG_MERGE.equals(name)) {
                    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 {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // 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);
                        }
                    }

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

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

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

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (Exception e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                                + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            } 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),如果不是则先createViewFromTag(root, name, inflaterContext, attrs)创建一个view,然后调用root.generateLayoutParams(attrs)来生成LayoutParams,然后递归调用rInflateChildren(parser, temp, attrs, true)遍历所有的标签完成解析,最后root.addView(temp, params)完成界面加载.

最后附上一张布局文件解析流程图


LayoutInflater.png

新手发文,不吝赐教

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 有段时间没写博客了,感觉都有些生疏了呢。最近繁忙的工作终于告一段落,又有时间写文章了,接下来还会继续坚持每一周篇的...
    justin_pan阅读 561评论 0 2
  • RecyclerView Item 布局宽高无效问题探究 前言 这个问题很早之前就碰到过,后来通过google找到...
    TinyMen阅读 431评论 0 0
  • 一、适用场景 ListViewListview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用...
    Geeks_Liu阅读 10,761评论 1 28
  • 由LayoutInflater谈起 layoutInflater.inflate(int resource, Vi...
    肱二头肌的孤单阅读 733评论 2 8
  • 我试图改变你 却一无所得 白马在云彩地里奔腾 我在掩面痛哭 我是戈壁滩上孤飞的秃鹫 无尽的腐肉以滋养我的身躯 迷途...
    不堪寻阅读 226评论 2 1