LayoutInflater 源码分析
Posted 吴豪杰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LayoutInflater 源码分析相关的知识,希望对你有一定的参考价值。
0. 前言
LayoutInflater(布局填充器)
在安卓开发中,可以说是扮演着相当重要的角色,它让我们的 ListView
、 RecyclerView
等很容易变得多姿多彩,也正是它如此容易的操作,让它不由地多出了一份神秘…这篇博文将基于 android 6.0
对 LayoutInflater
的源码进行一定分析。
1. 获取实例
protected LayoutInflater(Context context)
mContext = context;
保护权限的构造方法使得获取 LayoutInflater
并不能直接使用 new
关键字,需要使用静态方法 from(context)
来从 SystemService
取得:
/**
* 从给定的Context获取实例
*/
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;
getSystemService(...)
的最终实现在 ContextImpl
调用的 SystemServiceRegistry
类中:
public static Object getSystemService(ContextImpl ctx, String name)
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
也就是从 SYSTEM_SERVICE_FETCHERS
这个常量 HashMap
中获取,那么这些系统服务是什么时候 put
进去的呢?答案就在类开头的静态代码块:
// 静态代码块 第一次访问此类(SystemServiceRegistry)时执行
static
// 注册辅助功能服务
registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
new CachedServiceFetcher<AccessibilityManager>()
@Override
public AccessibilityManager createService(ContextImpl ctx)
return AccessibilityManager.getInstance(ctx);
);
...
// 注册布局填充器服务
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>()
@Override
public LayoutInflater createService(ContextImpl ctx)
return new PhoneLayoutInflater(ctx.getOuterContext());
);
...
// 注册的时候放入SYSTEM_SERVICE_FETCHERS
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);
既然放进去了,那么如何取出呢?
return fetcher != null ? fetcher.getService(ctx) : null;
取出服务是通过 Fetcher
取出的, Fetcher
又是什么呢?
// ServiceFetcher是一个抽象接口
static abstract interface ServiceFetcher<T>
T getService(ContextImpl ctx);
// CachedServiceFetcher 抽象类为其实现
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T>
...
@Override
public final T getService(ContextImpl ctx)
// 取得缓存区
final Object[] cache = ctx.mServiceCache;
synchronized (cache)
// 从缓存区中获取
Object service = cache[mCacheIndex];
if (service == null)
// 如果缓存区中没有就调用createService(context)
service = createService(ctx);
// 并且放入缓存区
cache[mCacheIndex] = service;
return (T)service;
// 创建实例抽象方法
public abstract T createService(ContextImpl ctx);
好了,这下终于找到 LayoutInflater
的实例化入口了,就在 CachedServiceFetcher<T>
接口的实现中,也就是 registerService(...)
的第三个参数:
new CachedServiceFetcher<LayoutInflater>()
@Override
public LayoutInflater createService(ContextImpl ctx)
// 创建实例
return new PhoneLayoutInflater(ctx.getOuterContext());
这里 new
出了 PhoneLayoutInflater
实例,原来 PhoneLayoutInflater
才是我们使用到 LayoutInflater
:
// 继承自LayoutInflater
public class PhoneLayoutInflater extends LayoutInflater
...
2. 填充视图
inflate(...)
有多个方法重载,此处以参数较为全面的,也是常用的一个进行分析:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
// 获取Resources
final Resources res = getContext().getResources();
if (DEBUG)
Log.d(TAG, "INFLATING from resource: \\"" + res.getResourceName(resource) + "\\" ("
+ Integer.toHexString(resource) + ")");
// 传入一个Layout 返回该xml的解析器
final XmlResourceParser parser = res.getLayout(resource);
try
// 接着调用含有XmlPullParser参数的重载方法
return inflate(parser, root, attachToRoot);
finally
parser.close();
接下来就来到那个含有XmlPullParser参数的重载方法:
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;
// 参数root将作为返回初值 防止填充错误返回null
View result = root;
try
// 查找根节点
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT)
// 跳出循环这意味到达根节点
if (type != XmlPullParser.START_TAG)
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
final String name = parser.getName();
if (DEBUG)
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
// 如开头果是<merge />标签
if (TAG_MERGE.equals(name))
// 那么必须要展示在root布局中
if (root == null || !attachToRoot)
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
// 遍历子view
rInflate(parser, root, inflaterContext, attrs, false);
else
// 填充根ViewGroup
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null)
if (DEBUG)
System.out.println("Creating params from root: " +
root);
// 取得当前父容器的LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot)
// 如果attachToRoot为false 则应用父容器的LayoutParams
temp.setLayoutParams(params);
if (DEBUG)
System.out.println("-----> start inflating children");
// 遍历子view
rInflateChildren(parser, temp, attrs, true);
if (DEBUG)
System.out.println("-----> done inflating children");
// 如果attachToRoot为false 则调用父容器的addView方法
if (root != null && attachToRoot)
root.addView(temp, params);
// 其它情况直接返回填充好的view
if (root == null || !attachToRoot)
result = temp;
catch (XmlPullParserException e)
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
catch (Exception e)
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
finally
// 保存最后一次的Context 以便它用
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
// 事务结束
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
思路就是解析 xml
,对参数进行合理的应用,然后遍历子 view
,从而带领出两条路: 一个是填充根 view group
,一个是遍历子 view
,分别是以下两个后面将进行分析的方法:
// 根布局
createViewFromTag(root, name, inflaterContext, attrs);
// 子布局
rInflate(parser, root, inflaterContext, attrs, false);
还有一个重点是,这段代码让我们更加理解了 inflate(...)
后面两个参数 ViewGroup root
和 boolean attachToRoot
的联系:
root
指的是需要将填充好的view所放在的父容器attachToRoot
指的是是否链接到root父容器,如果为true,则调用父容器的addView()方法,否则应用父容器的LayoutParams
另外这里补充几个标签的知识:
<merge/>
、<include/>
和<View Stub/>
标签的使用与区别
<merge/>
将视图组合,减少UI层次,但只能用于根节点,比如include一个layout,这个layout中就可以用merge作为根节点,防止include后造成多层嵌套<include/>
包含一个layout布局,减少代码重复<View Stub/>
用于需要时才加载的布局,比如错误信息的展示
2.1 填充根布局
填充根布局调用 createViewFromTag(...)
来获取 View
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs)
// 调用下面的重载方法
return createViewFromTag(parent, name, context, attrs, false);
其实调用的是含包访问权限的其重载方法:
// 参数parent 需要显示在之上的父容器
// 参数name 解析xml中跟节点名字
// 参数attrs 主题属性
// 参数ignoreThemeAttr 是否忽略主题样式
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr)
// 如果标签是view
if (name.equals("view"))
// 则获取null命名空间中class属性的值
name = attrs.getAttributeValue(null, "class");
// 如果不忽略主题样式
if (!ignoreThemeAttr)
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0)
// 使用带有主题样式的ContextThemeWrapper来替换原有的context
context = new ContextThemeWrapper(context, themeResId);
ta.recycle();
// 如果标签是blink
if (name.equals(TAG_1995))
// Let's party like it's 1995!
// 1955是什么典故么?
// 返回 内部继承自FrameLayout的BlinkLayout
return new BlinkLayout(context, attrs);
try
// 使用非空的Factory调用onCreateView(...)方法
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);
if (view == null)
// 如果Factory都无法正常工作
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
// 则直接调用内部onCreateView(...)方法
try
// 判断是否包含'.'
// contains()方法内部也是通过调用indexOf()
if (-1 == name.indexOf('.'))
// 包含表明指定了完整类名
// 比如 android.support.v7.widget.CardView
view = onCreateView(parent, name, attrs);
else
// 否则传入null使用默认的前缀
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);
ie.initCause(e);
throw ie;
catch (Exception e)
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
经过一番折腾,原来真正的创建 View
在 createView(name, null, attrs)
方法中:
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
// 开始事务
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null)
// 如果构造器为获取到 就使用反射获取 并强制转换为View的class
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 允许客户端对其进行过滤 比如RemoteViews
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)
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[] args = mConstructorArgs;
args[1] = attrs;
// 使用构造器创建对象
final View view = constructor.newInstance(args);
if (view instanceof ViewStub)
// 如果是ViewStub 就为其设置LayoutInflater 以便后续inflate
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
return view;
catch (NoSuchMethodException e)
...
至此,根布局的填充算是完成了。
2.2 遍历子布局
接下来便是遍历子布局:
// 深度优先遍历
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))
parseRequestFocus(parser, parent);
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
// 如果是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中
viewGroup.addView(view, params);
if (finishInflate)
parent.onFinishInflate();
遍历子布局就是一个不断递归的过程,递归完毕parent就被充满了内容,这时返回到 inflate()
方法中,各种绚丽花哨的效果就被填充完毕,就可以随时进行展示了。
总结
LayoutInflater
是一个神奇有力的工具,将它用好,相信你的App一定会更加绚丽多姿,通过此文,希望你对 LayoutInflater
有更近一步的了解,对安卓源码的精妙设计也更感兴趣!
以上是关于LayoutInflater 源码分析的主要内容,如果未能解决你的问题,请参考以下文章
[Android FrameWork 6.0源码学习] LayoutInflater 类分析