从Activity#setContentView谈谈系统如何解析xml布局文件

说在前面

本次源码来自Android API 24 Platform

引子

我们通常都会在Activity#onCreate方法中调用Activity#setContentView,传入页面layout,关联Activity和布局文件。那么,Activity#setContentView到底发生了什么呢?系统是如何解析我们的xml文件的?

源码分析

Activity#setContentView

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

这里,很简单,主要是 getWindow().setContentView(layoutResID) 这一句。
首先, getWindow()返回了什么?

Activity#getWindow

    public Window getWindow() {
        return mWindow;
    }

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        //关键在这里
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }

        //代码省略
        ...
    }

在Activity#attach中,创建了这个window,而且是Window的子类PhoneWindow。至于attach方法什么时候调用,不是我们这个文章要讨论的。(具体什么时候调用这个方法可以查看ActivityThread#performLaunchActivity)

PhoneWindow#setContentView

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //创建并初始化DecorView,并给mContentParent 赋值
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //这里就是关键
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        //代码省略
        ...
    }

最后调用了mLayoutInflater.inflate方法,那mLayoutInflater是什么?

PhoneWindow#constructor

    /**
     * Constructor for main window of an activity.
     */
    public PhoneWindow(Context context, Window preservedWindow) {
        this(context);
        //代码省略
        ...
    }

    public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }

这里就引出了解析xml的关键:LayoutInflater

LayoutInflater#from

    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

从context.getSystemService得到LayoutInflater。
这个时候我们应该想到,这个context到底是什么?我们查看Context代码,发现Context只是一个抽象类。
PhoneWindow的构造函数来自于Activity#attach方法,这个context就是Activity。
所以我们来看看Activity#getSystemService

Activity#getSystemService

    @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        //因为此次我们传入的参数是Context.LAYOUT_INFLATER_SERVICE,所以我们最终走到了这里
        return super.getSystemService(name);
    }

ContextThemeWrapper#getSystemService

    @Override
    public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                //是的,最后,从这里返回了LayoutInflater
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

我觉得差不多已经绕晕了,其实,PhoneWindow中的LayoutInflater#from最后在ContextThemeWrapper#getSystemService执行了LayoutInflater.from(getBaseContext()).cloneInContext(this),而ContextThemeWrapper#getBaseContext返回的就是Context。这个Context来自于ContextWrapper#attachBaseContext方法,这个方法又在Activity#attach中执行。

Activity#attach

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        //就是这里了。这个context是attach的参数,
        attachBaseContext(context);
    }

前面我们说过了,attach方法在ActivityThread#performLaunchActivity中调用

ActivityThread#performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       //代码省略...
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            //代码省略...
        }
        //代码省略...

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            //代码省略...
            if (activity != null) {
               // activity.attach的第一 个参数Context由这个方法创建
                Context appContext = createBaseContextForActivity(r, activity);
            //代码省略...
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);
                }           
            }
            r.paused = true;
            //代码省略...
            mActivities.put(r.token, r);

        } 
            //代码省略...
        

        return activity;
    }

    private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
        //代码省略
          ...
      //这个就是我们想要的,Context实际创建的是Context的子类ContextImpl 
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.token, displayId, r.overrideConfig);
        appContext.setOuterContext(activity);
        Context baseContext = appContext;
         //代码省略
          ...
        return baseContext;
    }

感觉真的跑偏了哇,到这里,我们知道了Context,实际上是其子类ContextImpl。
那么,回到刚开始,我们知道LayoutInflater#from方法的参数是ContextImpl. 我们可以去ContextImpl查找ContextImpl#getSystemService

ContextImpl#getSystemService

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

无脑下一步

SystemServiceRegistry#getSystemService

    private static final HashMap> SYSTEM_SERVICE_FETCHERS =
            new HashMap>();
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
    private static  void registerService(String serviceName, Class serviceClass,
            ServiceFetcher serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

这个很简单,就是单例的一种,创建一个对象,保存在静态HashMap中,获取单例就从这个HashMap中获取之前设置的单例对象。
而这个SystemServiceRegistry#registerService什么时候调用呢?
我们可以看到,SystemServiceRegistry中的静态代码块中调用了这个方法。

    static {
        //代码省略...
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
          //代码省略...
    }

在静态代码块中生成了一堆对象放到HashMap中,其中就包括我们需要的key是Context.LAYOUT_INFLATER_SERVICE的对象。

    static abstract class CachedServiceFetcher implements ServiceFetcher {
        private final int mCacheIndex;

        public CachedServiceFetcher() {
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
            final Object[] cache = ctx.mServiceCache;
            synchronized (cache) {
                // Fetch or create the service.
                Object service = cache[mCacheIndex];
                if (service == null) {
                    service = createService(ctx);
                    cache[mCacheIndex] = service;
                }
                return (T)service;
            }
        }

        public abstract T createService(ContextImpl ctx);
    }

在SystemServiceRegistry#getSystemService中,我们最终返回的是fetcher.getService(ctx),而fetcher就是刚刚new出来的对象。
CachedServiceFetcher很简单,CachedServiceFetcher#getService返回的对象就是CachedServiceFetcher#createService创建的对象,在这里,就是我们需要知道的PhoneLayoutInflater。
现在,我们临时总结一下:

  1. LayoutInflater#from的参数context, 实际上是Context的子类ContextImpl.
  2. LayoutInflater#from方法返回的参数实际上是LayoutInflater的子类PhoneLayoutInflater
    虽然返回的是PhoneLayoutInflater,但是PhoneLayoutInflater并没有重新实现inflate方法。所以接下来,我们继续看LayoutInflater#inflate.

LayoutInflater#inflate

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        //代码省略...
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    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];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
               //遍历xml的节点,找到第一个节点是开始标签或者结束标签的节点
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                //如果找到的这个节点不是开始标签,就抛出以异常
                //上面的和下面的代码,就是为了找到xml中的第一个开始标签,从这个开始标签开始解析节点
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();
               //代码省略...
                if (TAG_MERGE.equals(name)) {
                    //如果xml的节点标签是merge,那么root 不能为空,attachToRoot也不能为false,
                    if (root == null || !attachToRoot) {
                        throw new InflateException(" can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //忽略掉merge,将merge标签的子元素添加到root上
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    //生成根节点,也就是布局文件的根节点View(开始标签)
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                       //代码省略...
                        // Create layout params that match root, if supplied
                        //生成该View的LayoutParams
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            //如果不需要添加到root,直接设置为该View设置LayoutParams
                            temp.setLayoutParams(params);
                        }
                    }
                    //代码省略...
                    // Inflate all children under temp against its context.
                    //遍历开始标签,将该标签的子元素添加进temp
                    rInflateChildren(parser, temp, attrs, true);
                    //代码省略...
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        //将该元素作为root的子元素,添加进root中
                        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;
                    }
                }
            } 
            //代码省略...
             finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            return result;
        }
    }

我们调用LayoutInflater#inflate的时候,调用的是两个参数的方法,其中,root来自于PhoneWindow#generateLayout, 查看这个方法,我们可以知道,root就是id为com.android.internal.R.id.content的元素,必然不是null,那么attachToRoot就是true了。也就是,解析xml中的元素,并把xml中的开始标签元素作为com.android.internal.R.id.content的子元素。

LayoutInflater#rInflateChildren和android.view.LayoutInflater#rInflate

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
    //该方法的作用就是,遍历parent的子元素,并将子元素通过addView添加进parent中
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        //循环遍历子元素
        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标签
                parseRequestFocus(parser, parent);
            } 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(" cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                //merge标签不能作为子元素标签,必须是根标签
                throw new InflateException(" 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);
                viewGroup.addView(view, params);
            }
        }

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

通过递归,完成所有标签的遍历,并将子元素通过addView添加进该标签的父控件中。其中,创建单个标签所表示的View也是通过LayoutInflater#createViewFromTag来实现。

LayoutInflater#createViewFromTag

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }
    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);
            //如果有设置theme属性,则重现生成context
            //ContextThemeWrapper就是ContextWrapper的子类,里面多了mThemeResource这个属性,
            //而ContextWrapper就是Context的装饰类
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
            //可以设置mFactory2 和mFactory ,实现特殊的解析逻辑
            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是长度为2的数组,数组的第一个元素是context,
                //如果没有设置theme,这个context就是Activity中的context,
                //也就是创建Activity的时候生成的那个ContextImpl
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        //如果节点标签的名字没有. 比如TextView,就是没有包名,前缀
                        view = onCreateView(parent, name, attrs);
                    } else {
                       //如果节点标签有包名,比如自定义控件,比如support中的控件(android.support.v7.widget.RecyclerView)
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } 
        //代码省略...
    }

该方法根据节点名称创建节点View.

LayoutInflater#onCreateView

    protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }

直接调用了两个参数的LayoutInflater#onCreateView,上文我们说过,关于LayoutInflater我们实际使用的是PhoneLayoutInflater, 而在PhoneLayoutInflater中,我们重写了两个参数的onCreateView方法。

PhoneLayoutInflater#onCreateView

    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;
                }
            } 
            //代码省略...
        }
        return super.onCreateView(name, attrs);
    }

该方法就是给name加上包名,比如TextView,我们一般写在xml中是TextView,而不是android.widget.TextView,那么,我们通过遍历sClassPrefixList,给TextView加上包名,去生成View控件,如果加上android.widget.,LayoutInflater#createView可以生成View,那么就停止遍历,如果没有生成,就继续使用android.webkit.作包名,如果三个都不行,使用LayoutInflater#onCreateView来生成View,而LayoutInflater#onCreateView也并没什么特殊的,只不过是包名作为android.view.来生成这个View.至此,我们知道,这个PhoneLayoutInflater#onCreateView,会逐个使用android.widget., android.webkit., android.app., android.view. 来作为View的包名,去生成View,如果View可以生成,就意味着包名正确,不再尝试后面的包名。

LayoutInflater#createView

    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        //如果sConstructorMap里面保存的有该类的构造函数,就直接使用
        Constructor constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class clazz = null;

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                 //如果sConstructorMap里面没有保存的有该类的构造函数,就通过反射去获取该类的构造
                // Class not found in the cache, see if it's real, and try to add it
                //如果有前缀(包名),则拼接包名和类名,如果没有前缀,直接使用name,
                //通过ClassLoader,获取类的Class对象
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
               //代码省略...
               //通过反射,获取类的构造函数,并保存到sConstructorMap中
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                //代码省略
            }
            //上文已经说过了,mConstructorArgs是长度为2的数组,数组的第一个元素是context
            Object[] args = mConstructorArgs;
            //现在把attrs赋值给数组的第二个元素
            args[1] = attrs;
            //通过反射,生成View对象
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                //针对ViewStub的特殊处理
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
        }
        //代码省略...
    }

你可能感兴趣的:(从Activity#setContentView谈谈系统如何解析xml布局文件)