天天看点

Android源码阅读分析:从资源文件到控件布局——LayoutInflater分析从资源文件到控件——LayoutInflater分析

从资源文件到控件——LayoutInflater分析

本文也发布于本人的知乎文章:https://zhuanlan.zhihu.com/p/394073666

(注:源代码为android-8.1)

0. 前言

我在我的文章《Android源码阅读分析:从Activity开始(二)——加载布局》中简单介绍了

Activity

的如何加载布局的。在文章末尾提到,资源文件通过

inflater

方法转换为

View

。那么本篇文章就来分析一下

LayoutInflater

是如何工作的。

1. 创建LayoutInflater

LayoutInflater

的构造方法被

protected

修饰,也就是说,我们在创建

LayoutInflater

时,不能直接使用构造方法来创建新对象。官方的推荐获取

LayoutInflater

对象方式有两个:

  - 通过

Activity.getLayoutInflater

方法获取

  - 通过

Context.getSystemService

方式获取

这样能够保证获取到的

LayoutInflater

对象是已经与当前

context

关联上。那么,我就分别从这两个方法来看一下是如何获取对象的。

1.1 通过

Activity.getLayoutInflater

获取

LayoutInflater

对象

(frameworks/base/core/java/android/app/Activity.java)

public LayoutInflater getLayoutInflater() {
	return getWindow().getLayoutInflater();
}
           

从上一篇文章中,我们知道了,

Activity.getWindow

方法所得到的是一个

PhoneWindow

对象,那么跟踪

PhoneWindow

getLayoutInflater

方法。

(framework/base/core/java/com/android/internal/policy/PhoneWindow.java)

public LayoutInflater getLayoutInflater() {
	return mLayoutInflater;
}
           

哦?这里就是简简单单的返回了

mLayoutInflater

。那么,我们就要看看这个变量是在哪里创建或者赋值的。

  经过查找,发现在

PhoneWindow

类里,只有一个地方是

mLayoutInflater

变量被赋值了。

(frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java)

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

这里调用了

LayoutInflater

from

方法。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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

方法,这与官方推荐的第二种方法相同。

1.2 通过

Context.getSystemService

方式获取

LayoutInflater

对象

getSystemService

Context

的一个方法,通过传入

NAME

来获取对应的

Object

,然后转换为相应的服务对象。这里传入的参数值为

Context.LAYOUT_INFLATER_SERVICE

。通过查找,在

ContextThemeWrapper

类中找到处理

Context.LAYOUT_INFLATER_SERVICE

的方法。

(frameworks/base/core/java/androidview/ContextThemeWrapper.java)

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

这里又调用了

from(getBaseContext)

,而

baseContext

通常情况下就是

ContextImpl

。那么跟踪到

ContextImpl

getSystemService

方法。

(frameworks/base/core/java/android/app/ContextImpl.java)

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

在这里我们发现,是从

SystemServiceRegistry

里获取的系统服务,再继续跟踪。

(frameworks/base/core/java/android/app/SystemServiceRegistry.java)

public static Object getSystemService(ContextImpl ctx, String name) {
	ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}
           

从这里可以看出,

LayoutInflater

是从

SYSTEM_SERVICE_FETCHERS

里获取的,而

SYSTEM_SERVICE_FETCHERS

是一个

HashMap

,继续跟踪代码,发现

Context.LAYOUT_INFLATER_SERVICE

是在静态代码块中调用静态方法

registerService

内存入

SYSTEM_SERVICE_FETCHERS

的。

(frameworks/base/core/java/android/app/SystemServiceRegistry.java)

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

到这里,我们终于发现了真正的

LayoutInflater

是怎么被创造出来的了。原来我们真正使用的

LayoutInflater

其实是它的子类

PhoneLayoutInflater

  那么,我们再看一下

PhoneLayoutInflater

类的

cloneInContext

方法。这个方法在

LayoutInflater

中是抽象方法。

(frameworks/base/core/java/com/android/internal/policy/PhoneLayoutInflater.java)

public LayoutInflater cloneInContext(Context newContext) {
	return new PhoneLayoutInflater(this, newContext);
}
           

通过这个方法,可以获取一个在新的

Context

下的

PhoneLayoutInflater

对象。

2. 构建View

通常情况下,我们使用

LayoutInflater

构建

View

时,会调用

inflate

方法,下面我们来看一下其实现。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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

继续跟踪。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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

首先根据

resourceId

Context

中获取到

Resource

对象,然后调用

getLayout

得到一个XML解析器,之后调用重载方法

inflate

(frameworks/base/core/java/android/view/LayoutInflater.java)

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
	sychornized(mConstructorArgs) {
		...
		final Context inflaterContext = mContext;
		final AttributeSet attrs = Xml.asAttributeSet(parser);
		...
		mConstructorArgs[0] = inflaterContext;
        View result = root;
        try {
	        // 获取根节点
	        int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
				// Empty
            }
            ...
            final String name = parser.getName();
            ...
            // merge标签
            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 {
				// XML资源文件中的根节点View
				final View temp = createViewFromTag(root, name, inflaterContext, attrs);
				...
				// 解析子节点
				rInflateChildren(parser, temp, attrs, true);
				...
				if (root != null && attachToRoot) {
					// 将解析出的根节点View添加到
					root.addView(temp, params);
                }
                if (root == null || !attachToRoot) {
					result = temp;
                }
			}
			...
        }
        return result;
	}
}
           

这个方法的主要功能是根据参数和布局资源文件解析根节点

View

并返回,其内部调用了

rInflate

方法和

rInflateChildren

方法。其中,

rInflateChildren

方法内部也调用了

rInflate

方法。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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)) {
            if (parser.getDepth() == 0) {
				throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            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);
            viewGroup.addView(view, params);
        }
	}

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

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

这个方法通过递归方法解析出整个资源文件的布局树。其中核心的实例化

View

的方法为

createViewFromTag

。那么下面就重点看一下

createViewFromTag

方法是如何实现的。

(frameworks/base/core/java/android/view/LayoutInflater.java)

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
	...
	try {
		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) {
			final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
	            // 如果name不含".",表示该控件为系统自带控件
				if (-1 == name.indexOf('.')) {
	                view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
			}
			...
        }
        return view;
	}
	...
}
           

在创建控件实例时,首先判断该控件是否为系统自带控件,如果是系统自带控件,则调用

onCreateView

方法,否则直接调用

createView

方法。我们先看一下

onCreateView

方法,该方法在

LayoutInflater

中其实就是在控件名称前加上

android.view.

前缀,之后再调用

createView

方法。

PhoneLayoutInflater

复写了

onCreateView

方法,其实只是增加了三个前缀

android.widget.

android.webkit.

android.app.

,最终仍然会调用

createView

方法。那么下面就看一下

createView

方法的实现。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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) {
			// 通过反射得到name对应的控件类
			clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
			...
			// 获取构造器
			constructor = clazz.getConstructor(mConstructorSignature);
			constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
		} else {
			if (mFilter != null) {
	            Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
	                clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
					...
                }
                ...
            }
		}
		...
		Object[] args = mConstructorArgs;
		...
		final View view = constructor.newInstance(args);
		...
		return view;
	}
	...
}
           

这个方法主要做了两件事,一个是获取或创建控件的构造器,另一个是通过构造器实例化控件。在创建控件的构造器时,用到了

mConstructorSignature

(frameworks/base/core/java/android/view/LayoutInflater.java)

static final Class<?>[] mConstructorSignature = new Class[] {Context.class, AttributeSet.class};
           

可以看到这里的得到的构造器是包含

Context

AttributeSet

类型的两个参数。也就是说,如果我们需要做自定义控件的话,那么必须要有这两个类型参数的构造方法,即

View(Context context, AttributeSet attrs)

方法。

  至此,我们完成了从布局XML资源文件到控件的转换过程。

3. 总结

本篇文章主要分析了源码中

LayoutInflater

的创建过程以及其如何将布局XML资源文件转换为控件布局的过程。

  总的来说,就是通过获取系统服务的方式,得到

LayoutInflater

对象。

LayoutInflater

则会对布局资源文件进行解析,根据解析得到的控件名称,获取控件构造器。再利用控件构造器创建控件实例。