Android LayoutInflater 源码详解

278 阅读15分钟

1. LayoutInflater 简单使用

在 Activity 的 onCreate 方法中使用 setContentView 方法加载布局,可以将 xml 格式的 layout 布局文件解析并展示到屏幕上。

来看一下 setContentView 的具体实现:

// androidx/appcompat/app/AppCompatDelegateImpl.java
@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

setContentView 方法内部使用了 LayoutInflater 来加载布局。LayoutInflater 是加载布局的核心。

LayoutInflater:布局填充器,主要应用于动态添加 View,将 XML 布局文件实例化到相应的 View 对象中。

除了通过 setContentView 方法加载布局,我们有时还会直接使用 LayoutInflater 加载布局。

Eg:使用 LayoutInflater 将一个按钮动态添加到 activity_main.xml 中:

新建一个布局文件 button_layout.xml:

<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button" >
</Button>

新建 activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
</LinearLayout>

新建 MainActivity.java:

public class MainActivity extends Activity {
 
    private LinearLayout mainLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mainLayout = (LinearLayout) findViewById(R.id.main_layout);
        // 1. 创建 LayoutInflater 对象
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        // 2. inflate 方法加载视图
        View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null);
        // 3. 添加视图
        mainLayout.addView(buttonLayout);
    }
 
}

接下来对使用 LayoutInflater 加载布局的过程进行源码解析。

2. LayoutInflater 源码解析

2.1 LayoutInflater 对象的创建

LayoutInflater 的基本用法,通过 from 获取 LayoutInflater 对象:

LayoutInflater layoutInflater = LayoutInflater.from(context);

form 方法对 getSystemService 进行了简单的封装:

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

传递不同的 Context 对象,获取到的 LayoutInflater 对象也不同。

注意参数 Context,这里可以是 Activity、Application 或 Service。

每一个 Activity 对应一个 LayoutInflater 对象,如果每次传递的 Context 对象都是同一个 Activity 对象,只会创建一个 LayoutInflater 对象。

LayoutInflater activityLayoutInflater1 = LayoutInflater.from(LayoutInflaterDemoActivity.this);
LayoutInflater activityLayoutInflater2 = LayoutInflater.from(LayoutInflaterDemoActivity.this);
LayoutInflater appLayoutInflater = LayoutInflater.from(getApplicationContext());
Log.i(TAG, "activityLayoutInflater1: "+ activityLayoutInflater1 + ";  activityLayoutInflater2: "+ activityLayoutInflater2 + ";  appLayoutInflater: " + appLayoutInflater);

打印结果:

activityLayoutInflater1: com.android.internal.policy.PhoneLayoutInflater@62615a3;
activityLayoutInflater2: com.android.internal.policy.PhoneLayoutInflater@62615a3;  
appLayoutInflater: com.android.internal.policy.PhoneLayoutInflater@7e110a0

以 Context 为 Activity 举例,分析 getSystemService 流程

如果传入 Context 为 Activity,则会调用 Activity 中的 getSystemService 方法:

// Activity.java
@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;
    }
    return super.getSystemService(name);
}

Activity 中只对 WINDOW_SERVICESEARCH_SERVICE 这两个 ServiceName 进行了处理。我们进入 Activity 的父类 ContextThemeWrapper 中查看 getSystemService 方法:

@Override
public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
            mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
        }
        return mInflater;
    }
    return getBaseContext().getSystemService(name);
}

LayoutInflater 为 null,使用 getBaseContext 新建一个 LayoutInflater 对象。否则直接返回 mInflater,不会重复创建。

getBaseContext 方法获取的就是该 Activity 绑定的 mBase 对象,这个 mBase 是一个 Context 对象 ,但 Context 是一个抽象类,那么它实际上是什么对象呢?

mBase 是一个 ContextImpl 对象,实际上会调用 ContextImpl 的 getSystemService 方法:

// android/app/ContextImpl.java
@Override
public Object getSystemService(String name) {
    if (vmIncorrectContextUseEnabled()) {
        // Check incorrect Context usage.
        if (WINDOW_SERVICE.equals(name) && !isUiContext()) {
            final String errorMessage = "Tried to access visual service "
                    + SystemServiceRegistry.getSystemServiceClassName(name)
                    + " from a non-visual Context:" + getOuterContext();
            final String message = "WindowManager should be accessed from Activity or other "
                    + "visual Context. Use an Activity or a Context created with "
                    + "Context#createWindowContext(int, Bundle), which are adjusted to "
                    + "the configuration and visual bounds of an area on screen.";
            final Exception exception = new IllegalAccessException(errorMessage);
            StrictMode.onIncorrectContextUsed(message, exception);
            Log.e(TAG, errorMessage + " " + message, exception);
        }
    }
    return SystemServiceRegistry.getSystemService(this, name);
}

在 ContextImpl 内部,又委托给了 SystemServiceRegistry。SystemServiceRegistry 主要负责缓存、注册和获取系统服务。在其内部的静态代码块中默认注册了大量系统服务,包括 WINDOW_SERVICE、LOCATION_SERVICE、LAYOUT_INFLATER_SERVICE 等等。

// android/app/SystemServiceRegistry.java
        
static {
    ...

    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
        @Override
        public LayoutInflater createService(ContextImpl ctx) {
            // LayoutInflater 实际上是 PhoneLayoutInflater 类型
            return new PhoneLayoutInflater(ctx.getOuterContext());
        }});

    ...
}

在 Java 中,静态代码块(static block)是一个在类加载时执行的代码块。它被用来进行类级别的初始化,比如静态变量的初始化或执行一些需要在类加载时就准备好的任务。静态代码块在类第一次被加载到 JVM 时执行,且只会执行一次。

看一下 SystemServiceRegistry 的 getSystemService:

// android/app/SystemServiceRegistry.java

private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
        new ArrayMap<String, ServiceFetcher<?>>();
        
public static Object getSystemService(ContextImpl ctx, String name) {
    if (name == null) {
        return null;
    }
    final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    ...
    final Object ret = fetcher.getService(ctx);
    ...
    return ret;
}

SYSTEM_SERVICE_FETCHERS 是一个静态的 Map 容器,在 static {} 代码块中注册的服务都将保存在该容器中。这里首先根据服务的 name 获取对应的 Fetcher,然后通过该 Fetcher 的 getService 方法创建相应 LayoutInflater 对象。

当我们首次获取某个服务类型时,fetcher.getService 会执行其内部的 createService 创建对应服务,然后每个服务都会被保存在 SystemServiceRegistry 中,这里实际间接保存在 Fetcher 中。

// 优先取缓存
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
    private final int mCacheIndex;

    CachedServiceFetcher() {
        // Note this class must be instantiated only by the static initializer of the
        // outer class (SystemServiceRegistry), which already does the synchronization,
        // so bare access to sServiceCacheSize is okay here.
        mCacheIndex = sServiceCacheSize++;
    }

    @Override
    @SuppressWarnings("unchecked")
    public final T getService(ContextImpl ctx) {
        final Object[] cache = ctx.mServiceCache;
        final int[] gates = ctx.mServiceInitializationStateArray;
        boolean interrupted = false;

        T ret = null;

        for (;;) {
            boolean doInitialize = false;
            synchronized (cache) {
                // Return it if we already have a cached instance.
                // 优先取缓存
                T service = (T) cache[mCacheIndex];
                if (service != null) {
                    ret = service;
                    break; // exit the for (;;)
                }

                // If we get here, there's no cached instance.

                // Grr... if gate is STATE_READY, then this means we initialized the service
                // once but someone cleared it.
                // We start over from STATE_UNINITIALIZED.
                // Similarly, if the previous attempt returned null, we'll retry again.
                if (gates[mCacheIndex] == ContextImpl.STATE_READY
                        || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
                    gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
                }

                // It's possible for multiple threads to get here at the same time, so
                // use the "gate" to make sure only the first thread will call createService().

                // At this point, the gate must be either UNINITIALIZED or INITIALIZING.
                if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
                    doInitialize = true;
                    gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
                }
            }

            if (doInitialize) {
                // Only the first thread gets here.

                T service = null;
                @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
                try {
                    // This thread is the first one to get here. Instantiate the service
                    // *without* the cache lock held.
                    service = createService(ctx);
                    newState = ContextImpl.STATE_READY;

                } catch (ServiceNotFoundException e) {
                    onServiceNotFound(e);

                } finally {
                    synchronized (cache) {
                        cache[mCacheIndex] = service;
                        gates[mCacheIndex] = newState;
                        cache.notifyAll();
                    }
                }
                ret = service;
                break; // exit the for (;;)
            }
            // The other threads will wait for the first thread to call notifyAll(),
            // and go back to the top and retry.
            synchronized (cache) {
                // Repeat until the state becomes STATE_READY or STATE_NOT_FOUND.
                // We can't respond to interrupts here; just like we can't in the "doInitialize"
                // path, so we remember the interrupt state here and re-interrupt later.
                while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
                    try {
                        // Clear the interrupt state.
                        interrupted |= Thread.interrupted();
                        cache.wait();
                    } catch (InterruptedException e) {
                        // This shouldn't normally happen, but if someone interrupts the
                        // thread, it will.
                        Slog.w(TAG, "getService() interrupted");
                        interrupted = true;
                    }
                }
            }
        }
        if (interrupted) {
            Thread.currentThread().interrupt();
        }
        return ret;
    }

    public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}

在 createService 方法,我们发现 LayoutInflater 的实际类型是 PhoneLayoutInflater。

2.2 LayoutInflater inflate 流程

LayoutInflater 中极其重要的一个方法,inflate 方法。

在获取到具体的 PhoneLayoutInflater 对象后,使用 inflate 方法从指定的 XML 文件中加载视图:

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

两个参数:

  1. resource:资源 id
  2. root:父布局,为生成层次结构的父级,没有可以传 null

接着会调用:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    ...

    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

root 不为空时,attachToRoot 默认为 true。三个参数:

  1. resource:资源id
  2. root:生成层次结构的父级,可选
  3. attachToRoot:是否将生成的层次结构添加到 root
    • 如果 root 为 null,attachToRoot 无论设置为什么都无意义。
    • 如果 root 不为 null,attachToRoot 设为 true,则会给加载的布局文件的指定一个父布局,即 root。
    • 如果 root 不为 null,attachToRoot 设为 false,则会将布局文件最外层的所有 layout 属性进行设置,当该 view 被添加到父 view 当中时,这些 layout 属性会自动生效。
    • 在不设置 attachToRoot 参数的情况下,如果 root 不为 null,attachToRoot 参数默认为 true。

无论使用哪个 inflate 方法加载布局,都会辗转到这个方法:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        ...
        // 获取 XML 的 AttributeSet
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        ...
        View result = root;

        try {
            // 将给定的分析器前进到第一个 START_TAG,根节点
            advanceToRootNode(parser);
            // 第一个 START_TAG 的标签名 
            final String name = parser.getName();
            ...
            // 如果是 merge 标签
            if (TAG_MERGE.equals(name)) {
                // 对于 merge 标签,必须有 root 并且 attachToRoot==true 
                // merge 标签必须作为 root 标签使用,并且不能用在子标签中
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }
                //r 代表 recurse 递归方法
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // 通过 Tag name 和 attrs 创建视图
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;
                // 存在根视图
                if (root != null) {
                    ...
                    // 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)
                        // attachToRoot = false,立即设置属性
                        temp.setLayoutParams(params);
                    }
                }

                ...

                // Inflate all children under temp against its context.
                // 循环遍历这个根布局下的子元素 
                // 递归方法,根据传入的 parser 包含的层级,加载此层级的子 vie w并挂载到 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.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;
                }
            }

        } 
        ...
        //attachToRoot为true,就返回root,反之false就返回加载的XML文件的根节点View。
        return result;
    }
}

LayoutInflater 使用 pull解析 方式解析布局文件。调用了 createViewFromTag 这个方法,并把节点名和参数传了进去。它是用于根据节点名来创建 View 对象的。在 createViewFromTag 方法的内部又会去调用 createView 方法,然后使用反射的方式创建出 View 的实例并返回。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
    boolean ignoreThemeAttr) {
    // 如果传入的视图名为"view",则尝试从属性集`attrs`中读取"class"属性作为视图的真正名字。
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    // 如果传入的 attr 中包含 theme 属性,则使用 attr 中的 theme。将 theme 属性绑定到 context 上面
    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();
    }

    try {
        // 尝试创建 View,该方法可能返回空值
        View view = tryCreateView(parent, name, context, attrs);

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                //如果 tryCreateView 方法中还没加载到 view。判断 name 中是否包含“.”
                if (-1 == name.indexOf('.')) {
                    //onCreateView 最终会调用到 createView(name, "android.view.", attrs);,会在View 名字前面添加"android.view."前缀。
                    view = onCreateView(context, parent, name, attrs);
                } else {
                    //如果没有“.”,就表明是 Android 原生的 View
                    view = createView(context, name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } 
    ...
}

看一下 tryCreateView 方法:

@UnsupportedAppUsage(trackingBug = 122360734)
@Nullable
public final View tryCreateView(@Nullable View parent, @NonNull String name,
    @NonNull Context context, @NonNull AttributeSet attrs) {
    // 如果 Tag 名为 TAG_1995,返回一个 BlinkLayout 对象
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    View view;
    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);
    }

    return view;
}

LayoutInflater.Factory2 继承 Factory 接口,是设计出来灵活构造 View 的接口,可以用来实现换肤或者替换 View 的功能,同时也是 AppcompatActivity 用来做兼容和版本替换的接口。允许开发者在 XML 布局文件被转换成 View 对象时,插入自己的逻辑处理。这可以用于动态地替换布局文件中的某些组件。Activity 实际上是实现了LayoutInflater.Factory2接口的:

Factory2Factory的扩展,它提供了一个额外的参数:父视图(parent view)。这使得开发者在创建视图时可以考虑到视图的父子关系,从而进行更加精细的控制。

public interface Factory {
    @Nullable
    View onCreateView(@NonNull String name, @NonNull Context context,
            @NonNull AttributeSet attrs);
}

public interface Factory2 extends Factory {
    @Nullable
    View onCreateView(@Nullable View parent, @NonNull String name,
            @NonNull Context context, @NonNull AttributeSet attrs);
}

Activity中:

public View onCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context, @NonNull AttributeSet attrs) {
    if (!"fragment".equals(name)) {
        return onCreateView(name, context, attrs);
    }

    return mFragments.onCreateView(parent, name, context, attrs);
}

所以我们可以直接在 Activity 里面重写 onCreateView 方法,这样就可以根据 View 的名字来实现我们的一些操作,比如换肤的操作,比如定义一个名字来表示某种自定义 View。可以看这样一个用法:

<PlaceHolder
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="include LinearLayout"
    android:textColor="#fff"
    android:textSize="15sp" />

然后我们在重写的onCreateView里面判断name:

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
    if ("PlaceHolder".equals(name)) {
        return new TextView(this, attrs);
    }
    return super.onCreateView(name, context, attrs);
}

通过 createView 方法进行反射,创建出 View 的实例并返回:

public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    // 确保 viewContext 和 name 不为 null
    Objects.requireNonNull(viewContext);
    Objects.requireNonNull(name);
    //sConstructorMap 缓存 constructor,取出名为 name 的构造器
    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);
        // 没有缓存构造器,就反射得到构造器并添加到sConstructorMap中以便后面使用。
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            // constructor 为 null,加载类
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);
            // 如果设置过滤器 mFilter
            if (mFilter != null && clazz != null) {
                // 是否允许加载这个类
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    // 不允许加载,执行下列逻辑,抛出异常: Class not allowed to be inflated
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
            // 获取类的构造器
            constructor = clazz.getConstructor(mConstructorSignature);
            // 设置构造器为可访问
            constructor.setAccessible(true);
            // 将构造器放入缓存
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor
            // 如果设置了构造器
            if (mFilter != null) {
                // Have we seen this name before?
                // 通过 name 从 mFilterMap 获取这个类的允许状态
                Boolean allowedState = mFilterMap.get(name);
                // 之前没有检查过这个类
                if (allowedState == null) {
                    // New class -- remember whether it is allowed
                    // 加载这个类
                    clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                            mContext.getClassLoader()).asSubclass(View.class);

                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    // 将结果放入 mFilterMap 中
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
        }
        // 设置构造器参数
        Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = viewContext;
        Object[] args = mConstructorArgs;
        args[1] = attrs;

        try {
            // 通过构造器和参数创建视图实例
            final View view = constructor.newInstance(args);
            // 如果视图是 ViewStub 类型,则进行特定的初始化,并返回创建的视图。
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
        } finally {
            mConstructorArgs[0] = lastContext;
        }
    } 
    ...
}

同样的,通过上面的代码我们知道LayoutInflater是通过反射拿到构造方法来创建View的,那众所周知反射是有性能损耗的,那么我们可以在 onCreateView 方法中判断名字直接 new 出来,当然也可以跟 AppcompatActivity 里面做的一样,做一些兼容的操作来替换成不同版本的View:

public final View createView(View parent, final String name, @NonNull Context context,
        View view = null;
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break;
            case "EditText":
                view = new AppCompatEditText(context, attrs);
                break;
            case "Spinner":
                view = new AppCompatSpinner(context, attrs);
                break;
            case "ImageButton":
                view = new AppCompatImageButton(context, attrs);
                break;
            ...
        }
...
        return view;
    }

这里只是创建一个根布局的实例。

接下来,调用 rInflateChildren() 方法来查找这个 View 下的子元素,每次递归完成后则将这个 View 添加到父布局当中。

// 递归方法
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    // 获取解析器初始深度
    final int depth = parser.getDepth();
    int type;
    // 是否处理焦点请求,初始为 false
    boolean pendingRequestFocus = false;
    // 递归结束条件:文档结束或返回到初始深度 保证当前循环只读取本层的 view
    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();
        //name 为 requestFocus 表示将当前控件设为焦点
        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        // tag 标签,自定义标签
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        // include 标签
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        // merge 标签做二次判断,确保标签不会出现在非 root 元素位置
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            // 若不是特殊标签,同样使用 createViewFromTag 方法创建 view 实例
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            // 用当前的 attrs 加载成 LayoutParams 设置给当前 View。
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            // 递归调用查找 view 下的子元素、同时每次递归完成后将这个 view 添加到父布局当中。
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }

    if (pendingRequestFocus) {
        parent.restoreDefaultFocus();
    }

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

parser.getDepth()

<!-- outside -->     0
  <root>                  1
    sometext                 1
      <foobar>         2
      </foobar>        2
  </root>              1
  <!-- outside -->     0
private void parseInclude(XmlPullParser parser, Context context, View parent,
        AttributeSet attrs) throws XmlPullParserException, IOException {
    int type;
     // include 标签只能用在 ViewGroup 内部
    if (!(parent instanceof ViewGroup)) {
        throw new InflateException("<include /> can only be used inside of a ViewGroup");
    }

    // Apply a theme wrapper, if requested. This is sort of a weird
    // edge case, since developers think the <include> overwrites
    // values in the AttributeSet of the included View. So, if the
    // included View has a theme attribute, we'll need to ignore it.
    // 如果有 theme 属性从当前 View 的 attrs 里面看是否有 theme 属性,如果有就重新创建 ContextThemeWrapper
    // 用当前的 theme 属性替换之前 ContextThemeWrapper 里面的 theme
    final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
    final int themeResId = ta.getResourceId(0, 0);
    final boolean hasThemeOverride = themeResId != 0;
    if (hasThemeOverride) {
        context = new ContextThemeWrapper(context, themeResId);
    }
    ta.recycle();

    // If the layout is pointing to a theme attribute, we have to
    // massage the value to get a resource identifier out of it.
    // 查看当前 view 的 attrs 里面是否有 layout 的 id,如果没有就返回0
    int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
    if (layout == 0) {
        //找不到先找这个layout属性的值,看layout属性的string是否为空。为空直接报异常,不为空才去找layoutId
        final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
        if (value == null || value.length() <= 0) {
            throw new InflateException("You must specify a layout in the"
                + " include tag: <include layout="@layout/layoutID" />");
        }

        // Attempt to resolve the "?attr/name" string to an attribute
        // within the default (e.g. application) package.
        //找不到,就尝试去“?attr/”下面找对应的属性
        layout = context.getResources().getIdentifier(
            value.substring(1), "attr", context.getPackageName());

    }

    // The layout might be referencing a theme attribute.
    if (mTempValue == null) {
        mTempValue = new TypedValue();
    }
    if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
        layout = mTempValue.resourceId;
    }

    if (layout == 0) {
        final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
        throw new InflateException("You must specify a valid layout "
            + "reference. The layout ID " + value + " is not valid.");
    }

    final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
        (ViewGroup) parent, /*attachToRoot=*/true);
    if (precompiled == null) {
        final XmlResourceParser childParser = context.getResources().getLayout(layout);

        try {
            final AttributeSet childAttrs = Xml.asAttributeSet(childParser);

            while ((type = childParser.next()) != XmlPullParser.START_TAG &&
                type != XmlPullParser.END_DOCUMENT) {
                // Empty.
            }

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(getParserStateDescription(context, childAttrs)
                        + ": No start tag found!");
            }

            final String childName = childParser.getName();

            if (TAG_MERGE.equals(childName)) {
                // The <merge> tag doesn't support android:theme, so
                // nothing special to do here.
                //如果是merge标签,不支持属性的设置。此处直接把parent作为父布局传入,也就是加载出来的子view直接挂到parent上。
                rInflate(childParser, parent, context, childAttrs, false);
            } else {
                final View view = createViewFromTag(parent, childName,
                    context, childAttrs, hasThemeOverride);
                final ViewGroup group = (ViewGroup) parent;
                //获取include设置的id和visible。
                //如果include设置了id和visible,会使用include设置的属性。
                final TypedArray a = context.obtainStyledAttributes(
                    attrs, R.styleable.Include);
                final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                a.recycle();

                // We try to load the layout params set in the <include /> tag.
                // If the parent can't generate layout params (ex. missing width
                // or height for the framework ViewGroups, though this is not
                // necessarily true of all ViewGroups) then we expect it to throw
                // a runtime exception.
                // We catch this exception and set localParams accordingly: true
                // means we successfully loaded layout params from the <include>
                // tag, false means we need to rely on the included layout params.
                //先尝试使用<include>标签的属性去创建params,判断的标准是有没有width/height属性
                //若没有使用view的属性去创建,然后调用view.setLayoutParams给View设置属性
                ViewGroup.LayoutParams params = null;
                try {
                    params = group.generateLayoutParams(attrs);
                } catch (RuntimeException e) {
                    // Ignore, just fail over to child attrs.
                }
                if (params == null) {
                    params = group.generateLayoutParams(childAttrs);
                }
                view.setLayoutParams(params);

                // Inflate all children.
                rInflateChildren(childParser, view, childAttrs, true);
                // 如果<include>标签设置了id和visibility属性则一定会替换里面的id和visibility属性                                                                            
                if (id != View.NO_ID) {
                    view.setId(id);
                }

                switch (visibility) {
                    case 0:
                        view.setVisibility(View.VISIBLE);
                        break;
                    case 1:
                        view.setVisibility(View.INVISIBLE);
                        break;
                    case 2:
                        view.setVisibility(View.GONE);
                        break;
                }

                group.addView(view);
            }
        } finally {
            childParser.close();
        }
    }
    LayoutInflater.consumeChildElements(parser);
}

最终会把最顶层的根布局返回,至此 inflate 过程全部结束。