一、LayoutInflater常见使用场景
介绍之前,先总结一下我们在哪里都使用过LayoutInflater:
1、在Activity中
LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(R.layout.activity_money,null);
2、在Fragment中
view = View.inflate(getActivity(), R.layout.fragment_new, null);
3、在Adapter中
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = View.inflate(context, R.layout.item_bad_record, null);
}
4、通过getSystemService得到LayoutInflater
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
上述的使用,是我们平常常见的使用方式,这些场景都有一个特点,它们都需要将一个XML布局文件转换为View,所以准确的说LayoutInflater的主要功能就是布局加载。
二、LayoutInflater的介绍
LayoutInflater属于 android.view包下,在LayoutInflater的头部有一段关于LayoutInflater的介绍:
/**
* Instantiates a layout XML file into its corresponding {@link android.view.View}
* objects. It is never used directly. Instead, use
* {@link android.app.Activity#getLayoutInflater()} or
* {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
* that is already hooked up to the current context and correctly configured
* for the device you are running on. For example:
*
* <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
* (Context.LAYOUT_INFLATER_SERVICE);</pre>
*
* <p>
* To create a new LayoutInflater with an additional {@link Factory} for your
* own views, you can use {@link #cloneInContext} to clone an existing
* ViewFactory, and then call {@link #setFactory} on it to include your
* Factory.
*
* <p>
* For performance reasons, view inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
* to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource
* (R.<em>something</em> file.)
*
* @see Context#getSystemService
*/
翻译一下:
1、LayoutInflater主要将XML文件实例化成相应的View对象
2、通过:
Activity#getLayoutInflater();
LayoutInflater inflater = (LayoutInflater)context.getSystemService (Context.LAYOUT_INFLATER_SERVICE);
这两个方法得到的关联上下文的LayoutInflater对象
3、通过自定义Factory为View创建相应的LayoutInflater,可以使用cloneInContext克隆已经存在的ViewFactory,调用setFactory设置新的Factory
4、由于性能的原因,XML文件的预处理是在Build过程中进行的
5、LayoutInflater不能加载未编译的XML文件,而且LayoutInflater只能加载,通过XmlPullParser解析的R文件资源
三、源码解析
通过跟踪上述使用情景跟踪,LayoutInflater创建方法主要有两种:
1、LayoutInflater inflater = LayoutInflater.from(Context context);
2、LayoutInflater inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
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。下面让我们来分析下getSystemService是怎么实现的。
(1)getSystemService
了解这个问题之前我们要清楚,Context是什么,平时我们经常说Context是上下文环境。其实Application,Activity,Service都会存在一个Context。它的具体实现类是ContextImpl。那么直接看ContextImpl:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
可以看到这部分让SystemServiceRegistry完成
final class SystemServiceRegistry {
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
new HashMap<Class<?>, String>();
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
private SystemServiceRegistry() { }
//加载类时注册所有服务
static {
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
...注册其他服务
}
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
public static String getSystemServiceName(Class<?> serviceClass) {
return SYSTEM_SERVICE_NAMES.get(serviceClass);
}
//添加服务到相应集合里面
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;
public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
Object service = cache[mCacheIndex];
if (service == null) {
service = createService(ctx);
cache[mCacheIndex] = service;
}
return (T)service;
}
}
public abstract T createService(ContextImpl ctx);
}
}
在虚拟机第一次加载该类时就会注册各种ServiceFatcher,包括LayoutInflater,这个是在一系列的registerService中实现的。然后将它们存储在SYSTEM_SERVICE_FETCHERS这个HashMap中,以后要用只需从中获取,注册是在静态代码块中进行的,也就是说它只会执行一次,保证实例的唯一性。这可以说是用容器来实现的单例模式。最后通过getSystemService获取相应的Service。
(2)PhoneLayoutInflater
上述代码可以看到添加LayoutInflaterService时是添加的PhoneLayoutInflater对象,看下PhoneLayoutInflater:
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
/**
* Instead of instantiating directly, you should retrieve an instance
* through {@link Context#getSystemService}
*
* @param context The Context in which in which to find resources and other
* application-specific things.
*
* @see Context#getSystemService
*/
public PhoneLayoutInflater(Context context) {
super(context);
}
protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
@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;
}
} catch (ClassNotFoundException e) {
}
}
return super.onCreateView(name, attrs);
}
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
}
它覆写了onCreateView 方法,其实就是为系统View加上相应的前缀。如TextView读出的完整路径会是android.widget.TextView。再调用createView方法,通过类的完整路径来构造View对象
(3)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) {
return inflate(parser, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
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();
//解析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的tag来解析layout根视图,name就是要解析的视图的类名,如LinearLayout
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
//如果attachToRoot为false,给temp设置布局参数
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
//解析temp下所有子view
context.
rInflateChildren(parser, temp, attrs, true);
//如果root不为空,且attachToRoot=true,那么将temp添加到父视图中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//如果root为空,返回temp
if (root == null || !attachToRoot) {
result = temp;
}
}
}
return result;
}
}
跟踪源码,最后解析标签都会用到下面rInflate方法,使用createViewFromTag创建View,
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
//1.获取树的深度,深度优先遍历
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();
//如果该节点为requestFocus
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
//如果该节点为tag
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
//解析include标签
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
//merge必须是根视图
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);
//递归调用rInflate,此时finishInflate为true
rInflateChildren(parser, view, attrs, true);
//将解解析到的View添加到ViewGroup中,也就是它的parent
viewGroup.addView(view, params);
}
}
//merge的为false
if (finishInflate) {
parent.onFinishInflate();
}
}
创建View的createViewFromTag:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//解析view标签
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
//如果需要该标签与主题相关,需要对context进行包装,将主题信息加入context包装类ContextWrapper
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();
}
//BlinkLayout是一种闪烁的FrameLayout,它包裹的内容会一直闪烁,类似QQ提示消息那种。
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
//设置Factory,来对View做额外的拓展,这块属于可定制的内容
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 && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
//如果此时不存在Factory,不管Factory还是Factory2,还是mPrivateFactory都不存在,那么会直接对name直接进行解析
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//如果name中包含.即为自定义View,否则为原生的View控件
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
根据源码可以将createViewFromTag分为三个流程:
对一些特殊标签,做分别处理,例如:view,TAG_1995(blink)
进行对Factory、Factory2的设置判断,如果设置那么就会通过设置Factory、Factory2进行生成View
如果没有设置Factory或Factory2,那么就会使用LayoutInflater默认的生成方式,进行View的生成
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
这段就是对自定义View和原生的控件进行判断,原生代码使用时不包含”.”。原生控件的解析方式 onCreateView :
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
然后调用的3个参数的onCreateView()方法
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
最终指向createView方法:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//判断构造器是否存在
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
//如果构造器不存在,这个就相当于Class之前是否被加载过,sConstructorMap就是缓存这些Class的Map
if (constructor == null) {
//通过前缀+name的方式去加载
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);
//缓存Class
sConstructorMap.put(name, constructor);
} else {
//如果Class存在,并且加载Class的ClassLoader合法
//这里先判断该Class是否应该被过滤
if (mFilter != null) {
//过滤器也有缓存之前的Class是否被允许加载,判断这个Class的过滤状态
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
//加载Class对象操作
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//判断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[] args = mConstructorArgs;
args[1] = attrs;
//如果过滤器不存在,直接实例化该View
final View view = constructor.newInstance(args);
//如果View属于ViewStub那么需要给ViewStub设置一个克隆过的LayoutInflater
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
使用View的完整路径将类加载到虚拟机中,通过构造函数来创建View对象,最后返回View。这个就解析了单个View。
那如果是一棵树,交给rInflate来处理,每解析一个View就会递归调用rInflate。View的结构一层包一层,解析完成后parent.addView(),整棵视图树就构建完毕。