Android 源码分析 - LayoutInflater建立View的流程分析

  在平常开发中,我常用LayoutInflater将一个xml布局初始化为一个View对象,可是对它内部原理的了解倒是少之又少。今天,咱们就来看看LayoutInflaterandroid

  本文主要内容:缓存

  1. LayoutInflater建立流程。咱们经过Activity或者LayoutInflater的from方法来建立一个对象,咱们去看看这俩方法有啥区别。
  2. View 建立流程。主要介绍LayoutInflater将一个xml解析成为一个View经历的过程。
  3. setContentView方法解析。分析了View的建立流程,咱们再来看看是怎么初始化ActivityContentView

  本文参考文章:bash

  1. 反思|Android LayoutInflater机制的设计与实现

1. LayoutInflater的建立流程

  熟悉LayoutInflater的同窗应该都知道,建立LayoutInflater对象有两种方式:app

  1. 经过ActivitygetLayoutInflater方法。
  2. 经过LayoutInflater的from方法。

  但是这俩方法有啥区别呢?这是本节须要解答的地方。ide

(1).Context结构图

  不过在此以前,咱们先来了解Context的继承类图。 布局

  可能有人会问,咱们分析LayoutInflater,为何还要去了解Context的结构呢?这是由于LayoutInflater自己就是系统的一个服务,是经过ContextgetSystemService方法来获取的。post

  根据源码咱们知道,全部的系统服务都是在SystemServiceRegistry类里面进行注册,而后统一在ContextImpl进行获取,固然也包括LayoutInflater性能

(2). 两种方法的区别

  咱们经过ActivitygetLayoutInflater方法获取的其实是Window里面的LayoutInflater对象,而Window的LayoutInflater对象是在构造方法里面初始初始化的:ui

public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }
复制代码

  此时这个Context就是Activity的对象。因此从本质上来看,ActivitygetLayoutInflater方法和LayoutInflater的from方法没有很大的区别,惟一区别的在于这个Context对象的不一样。咱们来看一看from方法:this

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;
    }
复制代码

  在from方法里面调用的是ContextgetSystemService方法,如今咱们必须得了解整个Context的继承体系。   假设这里的ContextActivity,那么这里调用的就是ContextgetSystemService方法

@Override
    public Object getSystemService(String name) {
        return mBase.getSystemService(name);
    }
复制代码

  那这里的mBase又是什么呢?从上面的类图咱们知道是ContextImpl的对象,怎么来证实呢?

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // ······
        // 1. 建立ContextImpl的对象
        ContextImpl appContext = createBaseContextForActivity(r);
        // ······
        // 2. 调用Activity的attach方法
                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.configCallback);
        // ······
    }
    //---------------Activity--------------------------
    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, ActivityConfigCallback activityConfigCallback) {
         // 将ContextImpl传递给父类
        attachBaseContext(context);
        // ·······
    }
    //---------------ContextWapper---------------------
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
复制代码

  整个调用链很是的清晰,分别是:ActivityThread#performLaunchActivity -> Activity#attach -> ContextWapper#attachBaseContext

  而后,咱们再去看看ContextImplgetSystemService方法:

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

  最终的对象是从SystemServiceRegistry里面获取的。

2. View的建立流程

  LayoutInflater是经过inflate方法将一个xml布局解析成为一个View。咱们都知道inflate方法一般有三个参数,分别是:resourcerootattachToRoot,表示的含义以下:

  1. resource:xml布局的id。
  2. root:解析成以后的View的父View,此参数只在attachToRoot为true才生效。
  3. attachToRoot:决定解析出来的View是否添加到root上。

  有人可能会好奇,为何须要第三个参数,这是由于咱们将xml解析成View不必定当即须要添加到一个ViewGroup中去,这是为何呢?想想RecyclerViewRecyclerView在初始化ItemView时,不是当即将ItemView添加进去,而是当ItemView 进入屏幕可见区域时才会添加,由于RecyclerView有预加载机制,会加载一部分屏幕外的ItemView

  咱们先看一下inflate方法:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            try {
                // ······
                // 1. 若是是merge标签,直接绕过merge标签,
                // 解析merge标签下面的View。
                if (TAG_MERGE.equals(name)) {
                    // ······
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 2.建立View
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    // ······
                    // 3.递归解析children
                    rInflateChildren(parser, temp, attrs, true);
                    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(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            }
            return result;
        }
    }
复制代码

  inflate方法里面一种作了2件事:

  1. 若是根View是merge,直接递归解析它的子View。
  2. 若是根View不是merge,先解析根View,而后在递归解析它全部的child。

  咱们分为两步来看,先看一下解析根标签。

(1). 根View的解析

  根View的解析与child的解析不同,是经过createViewFromTag方法来完成的:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
            // ······
            View view;
            // 1. 若是mFactory2不为空,优先让mFactory2处理
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
            // 2. 若是上面解析为空,再使用mPrivateFactory常识这解析
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    // 若是是系统widget包下的控件
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else { // 若是是第三方包或者自定义的控件
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
             // ······
    }
复制代码

  inflate方法里面主要作了三件事:

  1. 首先使用mFactory2mFactory来尝试着建立View对象。mFactory2mFactory两者有且只能有一个有值,因此只须要调用其中一个就好了。
  2. 若是第一步中的两个工厂都没法建立View对象,再尝试着使用mPrivateFactory对象建立。不过一般来讲,这个对象都是为空的。
  3. 最后一步就是走兜底逻辑。这里的兜底有一点的特殊:若是View是widget的控件,会先在前面加一个android.wiget.的前缀,再行建立View;其次,若是是其余包下的控件,好比说,androidX和自定义的控件,就直接建立View对象。

  关于第一点,我还想介绍一下,Google爸爸之因此要设计两个工厂类,主要有3个方面的考虑:

  1. 兼容性,后面发布的版本能够兼容以前的版本,好比说,AppCompatActivity是新推出来的组件,因此在新版本上使用的mFactory2,旧版本就走原来的原来逻辑,也就是默认的onCreateView方法。
  2. 扩展性,若是开发者须要自定义一种全局的样式或者手动建立解析View,能够直接给LayoutInflayer设置Factory,用来达到本身的目的。
  3. 提高性能,这一点能够从能够从AppCompatActivity里面看出。AppCompatActivity的内部给LayoutInflayer设置了一个Factory2,也就是AppCompatDelegateImpl对象。AppCompatDelegateImpl在解析xml时,会先判断当前View是否基础控件,好比说,ButtonTextView或者ImageView等,若是是的话,能够经过new 的方式建立对应的AppCompatXXX对象。之因此说它提高性能,是由于它在解析基础控件时,再也不经过反射,而是经过new的方式建立的。

  上面的第三点可能有点复杂,咱们能够直接看一下AppCompatDelegateImplcreateView方法。因为createView内部调用了AppCompatViewInflatercreateView方法,因此这里咱们直接看AppCompatViewInflatercreateView方法:

final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;
        // ······
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                view = createView(context, name, attrs);
        }
        // ······
        return view;
    }
复制代码

  在默认的状况下,建立View对象的真正操做在createView方法里面,咱们能够来看看:

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
            // ······
            // 若是缓存中没有View的构造方法对象,
            // 那么就建立一个,而且放入缓存中去。
            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 {
                if (mFilter != null) {
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        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) {
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;
            // ······
    }
复制代码

  从这里,咱们就能够知道,LayoutInflater建立View的本质就是Java反射,因此在咱们平常开发过程当中,尽可能不要套太深的布局,毕竟反射的性能是有目共睹的。

(2). children的解析

  children的解析其实是在rInflate方法里面进行的,咱们直接来看源码:

void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
        // ······
        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();
            // requestFocus标签
            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)) { // merge标签
                throw new InflateException("<merge /> must be the root element");
            } else { // 正常View或者ViewGroup
                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);
            }
        }
        // ······
    }
复制代码

  children的遍历就像是一个树的遍历,就是一种广搜的思想,这里就不过多的讨论了。

3. AppCompatActivity的setContentView方法解析

  说完了上面的原理,最后咱们在来看看AppCompatActivitysetContentView方法。在LayoutInflater方面,AppCompatActivity相比于Activity,给LayoutInflater设置了一个Factory2,也就是上面讨论的东西。

  这里咱们再也不讨论以前谈论过的东西,而是看一个有趣的东西,我也不知道Google爸爸是怎么想的。   AppCompatActivityonCreate方法内部会给LayoutInflater设置一个Factory2对象,整个调用链是:AppCompatActivity#onCreate -> AppCompatDelegateImpl#installViewFactory -> LayoutInflaterCompat#setFactory2 -> LayoutInflaterCompat#forceSetFactory2。咱们直接来看setFactory2方法:

private static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
        if (!sCheckedField) {
            try {
                sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");
                sLayoutInflaterFactory2Field.setAccessible(true);
            } catch (NoSuchFieldException e) {
                Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class "
                        + LayoutInflater.class.getName()
                        + "; inflation may have unexpected results.", e);
            }
            sCheckedField = true;
        }
        if (sLayoutInflaterFactory2Field != null) {
            try {
                sLayoutInflaterFactory2Field.set(inflater, factory);
            } catch (IllegalAccessException e) {
                Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater "
                        + inflater + "; inflation may have unexpected results.", e);
            }
        }
    }
复制代码

  看到这个神奇操做没?万万没想到Google是经过反射的方式来给mFactory2方法。爸爸为啥要这样作呢?我猜想是setFactory2方法的坑,咱们来看看:

public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }
复制代码

  只要Factory被设置过,不管是Factory仍是Factory2,都不容许被再次设置。因此,我猜想是,爸爸为了成功给mFactory2设置上值,经过反射来绕开这种限制,这也是在是无奈。

  设置了Factory2工厂类以后,就是调用setContentView方法来给Activity设置ContentView。咱们这里直接来看一下AppCompatDelegateImplsetContentView方法:

@Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }
复制代码

  从这里咱们能够看出来,contentView是经过LayoutInflater加载出来的。具体的细节就再也不讨论了,上面已经详细的分析过了。

4. 总结

  到此为止,本文算是为止。总的来讲,本文仍是简单的(隐约的感受到,本文有点水),在这里,咱们对本文的内容作一个简单的总结。

  1. ActivitygetLayoutInflater方法和LayoutInflater在本质没有任何的区别,最终都会调用到ContextImplgetSystemService方法里面去。
  2. LayoutInflater初始化View分为三步:1.调用mFactory2或者mFactory方法来解析xml;2. 经过mPrivateFactory来解析xml;3. 经过onCreateView或者createView方法来解析xml。除了在AppCompatDelegateImpl在解析基础控件时使用的是new方式,其他几乎都是反射方式来建立View,因此在布局中尽量的少写View或者尽可能不要书写层次很深的布局。
相关文章
相关标签/搜索