本文分析版本: Android API 23,v4基于 23.2.1
1 简介
实例化布局的XML文件成相应的View对象。它不能被直接使用,应该使用
getLayoutInflater()
或getSystemService(Class)
来获取已关联了当前Context
并为你正在运行的设备正确配置的标准LayoutInflater实例对象。 例如:
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
为了创建一个对于你自己的View来说,附加了
LayoutInflater.Factory
的LayoutInflater
,你需要使用cloneInContext(Context)
来克隆一个已经存在LayoutInflater
,然后调用setFactory(LayoutInflater.Factory)
来替换成你自己的Factory。由于性能原因,View的实例化很大程度上依赖对于xml文件在编译时候的预处理。因此,目前使用
LayoutInflater
不能使用直接通过原始xml文件获取的XmlPullParser
,只能使用一个已编译的xml资源返回的XmlPullParser
((R.something file.)。
2 获取LayoutInflater的三种方式:
LayoutInflater inflater = getLayoutInflater();
//调用Activity的getLayoutInflater()LayoutInflater inflater = LayoutInflater.from(context);
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
但是,这三种方式本质上还是一样的:
-
getLayoutInflater()
调用的还是Activity中的方法:public LayoutInflater getLayoutInflater() { return getWindow().getLayoutInflater(); }
其中
Window
是一个抽象类,它 唯一 实现类是PhoneWindow
。com/android/internal/policyPhoneWindow.java
public PhoneWindow(Context context) { super(context); mLayoutInflater = LayoutInflater.from(context); } /** * Return a LayoutInflater instance that can be used to inflate XML view layout * resources for use in this Window. * * @return LayoutInflater The shared LayoutInflater. */ @Override public LayoutInflater getLayoutInflater() { return mLayoutInflater; }
可以看到,通过·
Activity
的getLayoutInflater()
最终调用的还是第二种方法。 -
通过
LayoutInflater.from(context);
获取LayoutInflater
对象。android/view/LayoutInflater.java
/** * Obtains the LayoutInflater from the given context. */ public static LayoutInflater from(Context context) { //通过获取系统服务的方式获取到LayoutInflater实例对象 LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
以上两种方法,最终还是调用了context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
获取LayoutInflater实例对象。
最终:不管以什么样的方式获取到LayoutInflater对象,最终都是通过系统服务来获取实例对象。
3 主要方法预览
-
public static LayoutInflater from(Context context);
获取LayoutInflater实例化对象。
-
public void setFactory(Factory factory);
设置使用当前LayoutInflater创建View的自定义实例化工厂。通过设置自定义工厂,可以在系统实例化View的时候进行一些拦截操作,比如可以把本来的TextView拦截成Button、给TextView统一指定字体等。
-
public void setFactory2(Factory2 factory);
同上,区别是工厂2多了对实例化View的时候Parent的支持。在API 11引入。
-
public void setFilter(Filter filter);
给当前LayoutInflater设置过滤器,如果要被填充的View不被这个过滤器允许,则会抛出InflateException。这个过滤器会覆盖当前LayoutInflater之上的任何之前设置过的过滤器。
-
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root);
把指定的布局资源填充成View。如果root不为空则把填充的View添加到root上,如果root为空则不添加。
-
public View inflate(XmlPullParser parser, @Nullable ViewGroup root);
通过布局xml资源的解析器把布局资源填充成View。如果root不为空则把填充的View添加到root上,如果root为空则不添加。
-
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);
把指定的布局资源填充成View。如果root不为空并且attachToRoot为true,则把填充的View添加到root上,否则不添加。
-
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot);
通过布局xml资源的解析器把布局资源填充成View。如果root不为空并且attachToRoot为true,则把填充的View添加到root上,否则不添加。
-
public final View createView(String name, String prefix, AttributeSet attrs);
通过View的名称,前缀和attrs属性实例化View。
-
protected View onCreateView(String name, AttributeSet attrs);
通过View的父View,View名称和attrs属性实例化View(最终调用的是
createView(String name, String prefix, AttributeSet attrs);
)。 -
protected View onCreateView(View parent, String name, AttributeSet attrs);
通过View的父View,View名称、前缀和attrs属性实例化View(最终调用的是
createView(String name, String prefix, AttributeSet attrs);
)。 -
View createViewFromTag(View parent, String name,Context context, AttributeSet attrs);
通过View的父View,View的名称、attrs属性实例化View(内部调用
onCreateView()
和createView()
)。 -
void rInflate(XmlPullParser parser, View parent, Context context, final AttributeSet attrs, boolean finishInflate);
解析Parent的子View并添加到Parent上(递归调用)。
4 流程预览
// 把xml布局资源或者通过资源解析器实例化View
inflate{
if(merge标签){
// 递归实例化根节点的子View
rInflate();
// 返回是父View(因为根节点是merge标签)
}else{
// 实例化根节点的View
createViewFromTag();
// 递归实例化跟节点的子View
rInflateChildren()
// 这个需要注意
if(父View是空或者不把填充的View添加到父View){
返回根节点View
}else{
返回父View
}
}
}
// 通过View的名称实例化View
createViewFromTag{
//各个工厂先onCreateView()
onCreateView()或者createView()
}
// 递归实例化Parent的子View
rInflate{
// 解析请求焦点
parseRequestFocus()
// 解析include标签
parseInclude()
// 通过View的名称实例化View
createViewFromTag()
rInflate
}
注意 ,在调用inflate
方法的时候,传入的参数不一样,返回的View可是有区别的,总结起来就是:
- 传入的父View为空或者不添加到父View上,则返回根节点的View。
- 其他(也就是根节点是merge、父View不为空且添加到父View上,返回的是父View)。
5 流程详情
关键字段:
/************************字段定义区**********************/
// 反射调用构造方法的两个参数
final Object[] mConstructorArgs = new Object[2];
// 反射构造方法的参数类型
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
protected final Context mContext;
注:以下我们用 “当前View”来指代该操作的View,用“父View”指代“当前View”的父View。
5.1 inflate方法解析
inflate 方法主要是把布局资源实例化成View并返回。
通过 获取LayoutInflater的三种方式 我们知道,通过布局文件填充成View对象最终调用的是下面两个方法:
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
if (DEBUG) System.out.println("INFLATING from resource: " + resource);
//获取布局资源的xml解析器,注意:在开头的时候我们强调过,普通的xml是不被支持的,必须是经过编译器处理过的。
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
最终调用的是此方法:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
// from传入的Context
final Context inflaterContext = mContext;
// 判断parser是否是AttributeSet,如果不是则用XmlPullAttributes去包装一下。
final AttributeSet attrs = Xml.asAttributeSet(parser);
// 保存之前的Context
Context lastContext = (Context) mConstructorArgs[0];
// 赋值为传入的Context
mConstructorArgs[0] = inflaterContext;
// 默认返回的是传入的Parent
View result = root;
try {
// 查找开始标签
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//如果没找到有效的开始标签则抛出InflateException
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)) {
// 根节点为空或者不添加到根节点上,则抛出异常。
// 因为“merge”标签必须是要被添加到父节点上的,不能独立存在。
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 递归实例化root(也就是传入Parent)下所有的View
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// temp是当前xml的根节点的View。通过父View、View名、Context、属性,来实例化View。也即实例化根节点的View。
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
// 如果传入Parent不为空
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 创建父View类型的LayoutParams参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 如果不把填充的View 关联在父View上,则把父View的LayoutParams参数设置给它
// 如果把填充的View关联在父View上,则会走下面addView的逻辑
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// 实例化根节点View下面的所有子View。
// TODO ..................
rInflate(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// Google建议关联所有找到的View
// 如果根节点不为null,并且需要把根节点View关联到Parent上,则使用addView方法把布局填充成的View树添加到Parent上。
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 决定返回的RootView(也即传入的Parent)还是xml中的根节点的View。
// 如果传入的Parent为空 或 实例化的View不添加到Parent上,则返回布局文件的根节点的View
// 否则,返回Parent
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (IOException e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
// 把这之前保存的Context从新放回全局变量中。
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
5.2 createViewFromTag方法解析
createViewFromTag 方法主要通过要实例化View的父View、要实例化View的名称、Context上下文、属性值来实例化View。该方法主要做的操作有:
- 先尝试用用户设置的Factory以及自己私有的Factory来实例化View。
- 如果这几个Factory都没有实例化View,则调用
onCreateView
或者createView
来实例化View。
代码分析:
/*
* 缺省方法可见性,好让BridgeInflater能重写它。
* 根据父View、View名称、Context、属性实例化View。
*/
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// 如果是View标签,则用class指向的类的完整名称来替换当前名称。(我们都知道,用Fragment的时候,可指定 class="Fragment完整路径名",其他widget控件也类似)
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// 应用主题包装,如果允许并且已经被指定
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
// 获取Context中主题属性
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
// 如果包含了主题,则用ContextThemeWrapper包装一下
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
// 如果根标签是“1995”,则创建一个“BlinkLayout”(其实就是一个FrameLayout)
// ps:这个没见过在哪里用到过。
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
// 尝试通过 mFactory2 或者 mFactory来创建View,这两个是通过setFactory和setFactory2来设置的。
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// 如果没有设置自定义工厂并且LayoutInflater本身私有的View工厂不为空,则用私有View工厂创建View。
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
// 如果View还为空,也即没有工厂,或者工厂未能正确创建View,则尝试通过自身的方法实例化View
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
// 如果View标签中没有".",则代表是系统的widget,则调用onCreateView,这个方法会通过"createView"方法创建View
// 不过前缀字段会自动补"android.view."。
view = onCreateView(parent, name, attrs);
} else {
// 非系统控件,则name本身就是控件的完整路径名。
//通过widget完整路径名以及属性创建View。
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;
}
}
5.3 onCreateView和createView方法解析
onCreateView 有两个重载方法,最终调用的是createView(String name, String prefix, AttributeSet attrs)
。
createView 主要做的操作有:
- 先通过Filter,看是否过滤。
- 利用反射实例化View对象。
源码分析:
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
// 系统控件,前缀自动补上"android.view."
return createView(name, "android.view.", attrs);
}
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 通过以View的name为key,查询构造函数的缓存map中时候已经有该View的构造函数。
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
// 构造函数在缓存的map中没有,则尝试去创建并添加。
if (constructor == null) {
// 通过 类名去加载控件的字节码
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//如果有自定义的过滤器并且加载到字节码,则通过过滤器判断是否允许加载该View。
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 {
// setFilter()可能会在类的构造函数被添加到map之后,所以获取到map中的构造函数后还需要判断是否过滤。
if (mFilter != null) {
// 过滤的map中是否已经包含了此类名。
Boolean allowedState = mFilterMap.get(name);
// 当前类名没有被放到过滤的缓存map中
if (allowedState == null) {
// 重新加载类的字节码
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 重新通过过滤器判断是否过滤。
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
// 把过滤结果放到过滤的缓存map中。
mFilterMap.put(name, allowed);
if (!allowed) {
// 如果要过滤,则抛出异常。
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
// 缓存构造函数的map中已经保存了当前要实例化的View的构造函数并且是要过滤的,抛出异常。
failNotAllowed(name, prefix, attrs);
}
}
}
// 实例化类的参数数组,0 是获取LayoutInflater传入的Context,1 是View的属性
Object[] args = mConstructorArgs;
args[1] = attrs;
// 通过构造函数实例化View(看到此行就知道,为什么自定义View或者ViewGroup的时候,如果在布局中使用的话,必须重写两个参数的构造函数了。)
final View view = constructor.newInstance(args);
// 如果当前View是ViewStub,则把布局填充器设置给它。(因为ViewStub在此刻并不会填充期子View,而是等需要的时候由用户手动触发。)
if (view instanceof ViewStub) {
// 把当前LayoutInflater的克隆传递给ViewStub,让ViewStub实例化的时候用,因为ViewStub只是在需要的时候才会实例化View。
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} catch (NoSuchMethodException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a View subclass
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()));
ie.initCause(e);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
5.4 rInflateChildren和rInflate方法解析
rInflate方法主要是遍历传入的Parent的子节点,实例化Parent的所有子View。
当遍历的时候发现当前View还有子View则递归调用此方法继续实例化当前View的子View。
这里面主要的操作有:
- parseRequestFocus(),处理请求焦点。
- parseInclude(),处理include标签。
- 实例化1995或一般View并添加到当前View的父View上。
源码分析:
/**
* 循环方法用来深入xml的层级并且实例化内部的View(非根节点View),这个方法通过调用rInflate,并使用Parent的Context作为
* 实例化View的Context。
*/
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
/**
* 循环方法用来深入xml的层级并且实例化view以及View的子View,
* 最后调用onFinishInflate()方法
*/
void rInflate(XmlPullParser parser, View parent,Context context, final AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
// 获取当前xml解析的深度
final int depth = parser.getDepth();
int type;
// 循环遍历xml节点
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)) {
// 解析<tag>元素,并且设置键控标签在它包含的View上。最终调用的是View的view.setTag(key, value);方法
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
//处理include标签
if (parser.getDepth() == 0) {
// 最外层使用include标签抛出异常。
throw new InflateException("<include /> cannot be the root element");
}
// 解析include标签引入的布局
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
// 如果是merge标签则抛出异常(因为此方法实例化的是xml根节点的子View,所以非根节点不能使用merge标签。)
throw new InflateException("<merge /> must be the root element");
} else {
// 一般性的View
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
// 获取父View的LayoutParams,并在把View添加到父View的时候带过去
//(这里解释了,为什么自己手动new 一个View,添加到父View上的时候需要new父View的LayoutParams参数而不是自己的LayoutParams参数。)
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 递归遍历实例化
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
// 如果父View下的所有View都完成填充,则调用父View的onFinishInflate()方法。
if (finishInflate) parent.onFinishInflate();
}
5.5 parseInclude方法解析
/**
* 解析include标签
*/
private void parseInclude(XmlPullParser parser, Context context, View parent,
AttributeSet attrs) throws XmlPullParserException, IOException {
int type;
// 如果include标签的父View是ViewGroup,则继续,否则抛出异常。
if (parent instanceof ViewGroup) {
// 使用主题包装。如果include中的View有自己的attr属性,则忽略。
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();
// 获取Layout标签指向布局资源id
int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
if (layout == 0) {
// 获取Layout的value值
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" />");
}
// 尝试解析"?attr/name"成id资源
layout = context.getResources().getIdentifier(value.substring(1), null, null);
}
// include的布局可能引用了主题属性
if (mTempValue == null) {
mTempValue = new TypedValue();
}
// 尝试从主题中获取布局资源id,如果有的话。
if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
layout = mTempValue.resourceId;
}
// 如果还是无法找到布局id,抛出异常。
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.");
} else {
// 获取include标签中布局的解析器
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(childParser.getPositionDescription() +
": No start tag found!");
}
// 获取标签名
final String childName = childParser.getName();
// 实例化merge标签
if (TAG_MERGE.equals(childName)) {
// <merge>标签不支持android:theme,所以不需要其他处理
rInflate(childParser, parent, context, childAttrs, false);
} else {
// 创建View实例化对象
final View view = createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
// 获取View的id和可见性
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();
// 尝试加载<include />标签中的布局参数,如果父View无法生成布局参数(比如include标签下没有宽高参数是要抛出运行时异常的)
// 捕获运行时异常,然后用引用的Layout的attrs来创建布局参数
ViewGroup.LayoutParams params = null;
try {
// 使用include标签的attrs来生成布局参数
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
// Ignore, just fail over to child attrs.
}
if (params == null) {
// 如果include标签的attrs没有正确的生成布局参数,则使用Layout布局的attrs来生成布局参数
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
// 实例化所有子View
rInflateChildren(childParser, view, childAttrs, true);
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;
}
// 把布局实例化的View添加到父View上。
group.addView(view);
}
} finally {
childParser.close();
}
}
} else {
throw new InflateException("<include /> can only be used inside of a ViewGroup");
}
LayoutInflater.consumeChildElements(parser);
}
6 LayoutInflaterCompat
LayoutInflater我们用的很多,一般都是用来把布局填充成View,它里面有两个方法:
// api 1引入
setFactory(Factory factory)
// api 11 引入
setFactory2(Factory2 factory)
这里面需要传入接口的实现类,如下:
public interface Factory {
/**
* Hook you can supply that is called when inflating from a LayoutInflater.
* You can use this to customize the tag names available in your XML
* layout files.
*
* <p>
* Note that it is good practice to prefix these custom names with your
* package (i.e., com.coolcompany.apps) to avoid conflicts with system
* names.
*
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
*
* @return View Newly created view. Return null for the default
* behavior.
*/
public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
/**
* Version of {@link #onCreateView(String, Context, AttributeSet)}
* that also supplies the parent that the view created view will be
* placed in.
*
* @param parent The parent that the created view will be placed
* in; <em>note that this may be null</em>.
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
*
* @return View Newly created view. Return null for the default
* behavior.
*/
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
通过上述接口可以看出来,新的setFactory2(Factory2 factory)
比老的setFactory(Factory factory)
在构建View的时候多传入了一个Parent View。如果你想用setFactory2(Factory factory)
需要实现带Parent View和不带Parent View的两个方法,比较复杂,所以v4包中的LayoutInflaterCompat就为我们提供了兼容性处理,先看用法:
LayoutInflater layoutInflater = getLayoutInflater();
LayoutInflaterCompat.setFactory(layoutInflater, new LayoutInflaterFactory() {
@Override
public View onCreateView(View parent, String name, Context context,
AttributeSet attrs) {
// name 是布局文件中View的名称,在这里可以坐很多操作,比如:
// 给TextView设置字体。
// 把TextView变成Button,如果需要的话。
// 修改后的View,如果返回空则会调用LayoutInflater本身实例化View的方法,详情见createViewFromTag中try下面的逻辑。
return null;
}
});
举个例子: 现在我们用AS开发,一般默认是继承AppCompatActivity
,在初始化的时候:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// 获取兼容包的委托类
final AppCompatDelegate delegate = getDelegate();
// 安装Factory
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
if (delegate.applyDayNight() && mThemeId != 0) {
// If DayNight has been applied, we need to re-apply the theme for
// the changes to take effect. On API 23+, we should bypass
// setTheme(), which will no-op if the theme ID is identical to the
// current theme ID.
if (Build.VERSION.SDK_INT >= 23) {
onApplyThemeResource(getTheme(), mThemeId, false);
} else {
setTheme(mThemeId);
}
}
super.onCreate(savedInstanceState);
}
在AppCompatDelegate
的实现类AppCompatDelegateImplV7
中,·installViewFactory()
:
@Override
public void installViewFactory() {
// 使用LayoutInflaterCompat进行兼容性适配
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
// 如果之前没有设置工厂,则自己实现接口然后传入,用来实现AppCompat特性,比如支持向低版本 tint着色等新特性。
// 实际上它也是在工厂的实现类中用AppCompatXXX去替换XXX,比如用AppConpatTextView替换TextView等诸如此类。
LayoutInflaterCompat.setFactory(layoutInflater, this);
} else {
// 如果已经设置了Factory并且不是当前类,则什么也不做,只打印日志。这样就会失去上述中tint等特性。
if (!(LayoutInflaterCompat.getFactory(layoutInflater)
instanceof AppCompatDelegateImplV7)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
上述方法实际上调用LayoutInflaterCompat的下面的方法:
public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
IMPL.setFactory(inflater, factory);
}
IMPL是LayoutInflaterCompat中内部接口LayoutInflaterCompatImpl的实现类。这个接口有三个实现类:
- LayoutInflaterCompatImplBase。
- LayoutInflaterCompatImplV11。
- LayoutInflaterCompatImplV21。
static final LayoutInflaterCompatImpl IMPL;
static {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
// 5.0及其以上版本
IMPL = new LayoutInflaterCompatImplV21();
} else if (version >= 11) {
// 3.0及其以上版本
IMPL = new LayoutInflaterCompatImplV11();
} else {
// 低于3.0版本
IMPL = new LayoutInflaterCompatImplBase();
}
}
因此,调用LayoutInflaterCompat的setFactory方法,实际是调用对应版本的IMPL的setFactory方法。
-
低于3.0版本
LayoutInflaterCompatImplBase中调用的是LayoutInflaterCompatBase的setFactory方法,
static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) { inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null); } static LayoutInflaterFactory getFactory(LayoutInflater inflater) { LayoutInflater.Factory factory = inflater.getFactory(); if (factory instanceof FactoryWrapper) { return ((FactoryWrapper) factory).mDelegateFactory; } return null; }
其中,
FactoryWrapper
是LayoutInflaterCompatBase
的静态内部类:class LayoutInflaterCompatBase { static class FactoryWrapper implements LayoutInflater.Factory { final LayoutInflaterFactory mDelegateFactory; FactoryWrapper(LayoutInflaterFactory delegateFactory) { mDelegateFactory = delegateFactory; } @Override public View onCreateView(String name, Context context, AttributeSet attrs) { return mDelegateFactory.onCreateView(null, name, context, attrs); } public String toString() { return getClass().getName() + "{" + mDelegateFactory + "}"; } } static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) { inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null); } static LayoutInflaterFactory getFactory(LayoutInflater inflater) { LayoutInflater.Factory factory = inflater.getFactory(); if (factory instanceof FactoryWrapper) { return ((FactoryWrapper) factory).mDelegateFactory; } return null; } }
核心的方法就是
FactoryWrapper
的onCreateView
,可以看到它调用的是带有Parent View参数的onCreateView
方法,不过Parent View传的是null。 -
大于3.0小于5.0
LayoutInflaterCompatImplV11中调用的就是LayoutInflaterCompatHC的setFactory方法,LayoutInflaterCompatHC中:
static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) { // 如果传入的Factory不为空则包装一下,否则传空 final LayoutInflater.Factory2 factory2 = factory != null ? new FactoryWrapperHC(factory) : null; // 设置Factory2. inflater.setFactory2(factory2); // 获取当前Inflater的Factory。 final LayoutInflater.Factory f = inflater.getFactory(); // 如果属于Factory2则通过反射把mFactory赋值给mFactory2。 if (f instanceof LayoutInflater.Factory2) { // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21). // We will now try and force set the merged factory to mFactory2 forceSetFactory2(inflater, (LayoutInflater.Factory2) f); } else { // 否则设置mFactory2为新创建的Factory2。 // Else, we will force set the original wrapped Factory2 forceSetFactory2(inflater, factory2); } } // 利用反射修改Factory2 /** * For APIs >= 11 && < 21, there was a framework bug that prevented a LayoutInflater's * Factory2 from being merged properly if set after a cloneInContext from a LayoutInflater * that already had a Factory2 registered. We work around that bug here. If we can't we * log an error. * 对于版本>- 11 并且 < 21,如果调用cloneInContext从LayoutInflater克隆一个LayoutInflater,在FrameWork层有一个bug阻止了LayoutInflater的Factory2的合并,因为已经有一个Factory2被注册了,所在在此通过反射的方式去修改Factory2。 */ static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) { if (!sCheckedField) { try { sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2"); sLayoutInflaterFactory2Field.setAccessible(true); } catch (NoSuchFieldException e) { Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class " + LayoutInflater.class.getName() + "; inflation may have unexpected results.", e); } sCheckedField = true; } if (sLayoutInflaterFactory2Field != null) { try { sLayoutInflaterFactory2Field.set(inflater, factory); } catch (IllegalAccessException e) { Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater " + inflater + "; inflation may have unexpected results.", e); } } }
bug的具体产生原因见:LayoutInflater在Api 21以下的setFactory2的bug是怎么产生的
-
大于5.0
static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) { inflater.setFactory2(factory != null ? new LayoutInflaterCompatHC.FactoryWrapperHC(factory) : null); }
这个其实没啥用,可以跟第二条合并的,但是最新的v4包没有改,但是在api >= 20的Android源码里面,其实已经这么做了:
static final LayoutInflaterCompatImpl IMPL; static { final int version = Build.VERSION.SDK_INT; if (version >= 11) { IMPL = new LayoutInflaterCompatImplV11(); } else { IMPL = new LayoutInflaterCompatImplBase(); } }