前言
LayoutInflater对于我们Android开发来说应该是既熟悉又陌生。熟悉是因为我们所有的布局XML文件都是经过他来实例化为对应View实例,我们基本上经常跟他打交道;陌生是因为我们在开发的过程中是直接拿过来使用,对于他从何而来,除了实例化我们的布局文件还能用来做什么却不是很了解。
所以这篇文章重点讲下LayoutInflater原理
LayoutInflater 流程分析
获取LayoutInflater
@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {
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;
}
}
上面是LayoutInflater类中的静态方法可以获取LayoutInflater 实例。我们可以看到LayoutInflater是一个抽象类,不能直接实例化LayoutInflater 方法里面是通过context.getSystemService获取系统提供的服务,而且具体实例对象就是PhoneLayoutInflater,我们可以从服务注册的地方查到。
SystemServiceRegistry 注册 LayoutInflater 服务
在SystemServiceRegistry类中的静态语句块中我们可以找到注册方法,正是返回了PhoneLayoutInflater的实例。
static {
....
// 注册LayoutInflater 服务,这里创建的是PhoneLayoutInflater 的实例
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
//PhoneLayoutInflater第一次创建之后会缓存实例,下次会从缓存取
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
....
}
CachedServiceFetcher会缓存PhoneLayoutInflater实例,后面获取的都是缓存实例
View的加载
加载View的几个方法:
// 方法一
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();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
// 方法三
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
//1.从XML文件中获取属性集
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
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();
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 {
// Temp is the root view that was found in the xml
//2.解析属性集并创建返回xml中的根View temp
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
//3.给temp View 创建params
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
//4.创建temp View 下的所有子View,并建立他们的层级关系
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;
}
}
} 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;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
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;
boolean pendingRequestFocus = false;
// 遍历ziView
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 {
//5.遍历执行createViewFromTag创建childView
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);
}
}
.....
}
前两个方法最终都会调到第三个方法,所以我们主要看第三个方法,主要是5个关键步骤:
- 从XML解析初属性集合,创建view用
- 根据属性集合通过createViewFromTag方法创建temp View
- 给temp View创建布局参数,根据初始化传递进来的attachToRoot变量是否要将布局参数关联到temp View上
- 执行rInflateChildren解析childView 并建立他们的层级关系
- 所有的childView都需要通过createViewFromTag来创建,同第二个步骤一样
createViewFromTag做了什么
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 && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
//1.如果前面没有创建拦截创建View,那么久进入下面默认逻辑
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//判断name是否是view的全称 例如com.view.TextView
if (-1 == name.indexOf('.')) {
//name名称拼接全称,最后还是调用createView
view = onCreateView(parent, name, attrs);
} else {
//2.通过反射创建View
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
....
} catch (ClassNotFoundException e) {
....
} catch (Exception e) {
....
}
}
在创建我们View之前,我们可以看到mFactory2、mFactory、mPrivateFactory 三个LayoutInflater的成员变量hook View的创建。也就是说Api 提供了一个可能,我们可以按照我们的想法来创建View,而且我们只需要通过代码设置到LayoutInflater实例中去,这种设计模式代码的嵌入性少。
如果我们没有拦截View的创建的话,他将会走接下来默认的流程,最后调用createView方法创建View
Factory
factory 接口
public interface Factory {
public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
setFactory2是在SDK>11时候添加的,如果你是基于11以上的就使用setFactory2,否则就使用setFactory ,两者功能基本一致。当然你不想考虑兼容问题可以直接使用LayoutInflaterCompat.setFactory()。
设置Factory
/**
* Attach a custom Factory interface for creating views while using
* this LayoutInflater. This must not be null, and can only be set once;
* after setting, you can not change the factory. This is
* called on each element name as the xml is parsed. If the factory returns
* a View, that is added to the hierarchy. If it returns null, the next
* factory default {@link #onCreateView} method is called.
*
* <p>If you have an existing
* LayoutInflater and want to add your own factory to it, use
* {@link #cloneInContext} to clone the existing instance and then you
* can use this function (once) on the returned new instance. This will
* merge your own factory with whatever factory the original instance is
* using.
*/
public void setFactory(Factory 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 = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}
/**
* Like {@link #setFactory}, but allows you to set a {@link Factory2}
* interface.
*/
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 {
// 如果有factory 则用FactoryMerger将factory打包
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
/**
* @hide for use by framework
*/
public void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}
//PhoneLayoutInflater 中重载的方法
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
//使用责任链设计模式将两个facotory 打包到一起:优先调用新设置的factory的方法,如果返回为空再用之前的factory的方法,这里如果之前的factory也是FactoryMerger 实例,那么重复刚才的逻辑
private static class FactoryMerger implements Factory2 {
private final Factory mF1, mF2;
private final Factory2 mF12, mF22;
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}
public View onCreateView(String name, Context context, AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF2.onCreateView(name, context, attrs);
}
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
: mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
: mF2.onCreateView(name, context, attrs);
}
}
上面的三个设置factory的方法我们总结以下几点:
- setPrivateFactory是framework层使用的,我们不要使用。
- mFactorySet成员变量标示,导致我们一个PhoneLayoutInflater 只能设置一次Fractory,如果已经设置过factory 我们需要调用cloneInContext 复制一个实例进行设置 factory
- 通过cloneInContext复制的实例设置factory,他会创建FactoryMerger将之前的factory打包在一起,调用onCreateView的时候优先调用新设置的factory的方法
- 总结上面3点我们可以总结出,如果要给LayoutInflater设置全局的factory,需要在Application设置保证,因为Activity还是Fragment 都会有设置factory的代码(这里就不展开了,可以从源码中可以看出来)
Factory 有什么用?
使用LayoutInflater Factory的这一特性可以做很多事:
- 提高view构建的效率
- 当我们使用自定义view时,需要在xml中使用完整类名,系统实际就是根据完整类名进行反射构建。我们可以自己new出view避免系统反射调用,提高效率。
- 替换默认view实现,改变或添加属性
- 替换系统控件
- 一键换肤解决方案
总结
LayoutInflater Factory这一特性很强大,能做的远不止上面那些事。比如大部分“一键换肤”都是使用了这一特性来实现的。