- Notifications
You must be signed in to change notification settings - Fork 149
LayoutInflater
LayoutInflater是一个布局渲染工具,作用是根据xml布局文件构建View树。使用方式如下:
ViewitemView = LayoutInflater.from(context).inflate(R.layout.item_view,container,false);通过LayoutInflater.from静态方法获取LayoutInflater实例。源码如下:
publicstaticLayoutInflaterfrom(Contextcontext) {
LayoutInflaterLayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
thrownewAssertionError("LayoutInflater not found.");
}
returnLayoutInflater;
}可以看到这里通过context.getSystemService去获取一个类型为LAYOUT_INFLATER_SERVICE的服务。这个服务是什么呢?
上文中获取的这个LayoutInflater服务与AMS、WMS等服务不同,它是APP端子机虚拟的一个服务。其主要作用是,在本地调用创建PhoneLayoutInflater工具对象。在SystemServiceRegistry的静态代码块中有如下代码:
finalclassSystemServiceRegistry {
static {
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
newCachedServiceFetcher<LayoutInflater>() {
@OverridepublicLayoutInflatercreateService(ContextImplctx) {
returnnewPhoneLayoutInflater(ctx.getOuterContext());
}});
}
}也就是说,这里获取服务实际上是一个PhoneLayoutInflater对象。
LayoutInflater 构建 View 是从 inflate 方法开始的,在 LayoutInflater 中有多个 inflate 的重载方法,最终都会调用下面这个方法:
publicViewinflate(@LayoutResintresource, @NullableViewGrouproot, booleanattachToRoot) {
finalResourcesres = getContext().getResources();
// ...// 开启预编译,这里不讨论这种情况Viewview = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
returnview;
}
// 通过 Resource 得到布局对应的XmlResourceParserXmlResourceParserparser = res.getLayout(resource);
try {
// 继续调用重载的 inflate 方法returninflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}这里的 XmlResourceParser 是一个包含了 xml 文件信息的对象,Resource 的 getLayout 方法会通过 AssetManager 的一个 native 方法 loadResourceValue 去加载 xml 文件信息。最终这个xml文件会通过getResTable 从APK包的resources.arsc中取出,并最终封装成XmlResourceParser。这个过程不是重点内容,不去深究。
接下来调用的 inflate 方法会根据 XmlResourceParser 来创建 View。
publicViewinflate(XmlPullParserparser, @NullableViewGrouproot, booleanattachToRoot) {
synchronized (mConstructorArgs) {
// ... 省略了 Debug 相关代码finalContextinflaterContext = mContext;
finalAttributeSetattrs = Xml.asAttributeSet(parser);
ContextlastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
Viewresult = root;
try {
advanceToRootNode(parser);
finalStringname = parser.getName();
// 处理 merge 标签if (TAG_MERGE.equals(name)) {
// ...rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 通过 createViewFromTag 生成 xml 的 Root ViewfinalViewtemp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParamsparams = null;
if (root != null) {
// 如果提供了 root ,则创建跟随 root 的 layout paramsparams = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 如果不 attachToRoot 的话,则为这个View设置 LayoutParams temp.setLayoutParams(params);
}
}
// 生成子 ViewrInflateChildren(parser, temp, attrs, true);
// root 不为null,并且 attachToRoot 为 true,则将 temp 添加到 rootif (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
} // ... 省略 catch 相关代码returnresult;
}
}代码中注释写的比较详细,这里需要注意一下的是 createViewFromTag 这个方法。这个方法在后边代码中还会出现,所以这里先不做解读。接着看 rInflateChildren 方法的代码:
finalvoidrInflateChildren(XmlPullParserparser, Viewparent, AttributeSetattrs,
booleanfinishInflate) throwsXmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}只是调用了 rInflate 方法,rInflate源码如下:
voidrInflate(XmlPullParserparser, Viewparent, Contextcontext,
AttributeSetattrs, booleanfinishInflate) throwsXmlPullParserException, IOException {
finalintdepth = parser.getDepth();
inttype;
booleanpendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
finalStringname = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} elseif (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs);
} elseif (TAG_INCLUDE.equals(name)) { // 处理 include 标签if (parser.getDepth() == 0) {
thrownewInflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} elseif (TAG_MERGE.equals(name)) { // 处理 merge 标签thrownewInflateException("<merge /> must be the root element");
} else { // 解析 View finalViewview = createViewFromTag(parent, name, context, attrs);
finalViewGroupviewGroup = (ViewGroup) parent;
finalViewGroup.LayoutParamsparams = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}可以看到在这个 rInflate 方法中会对 include 标签、merge 标签等进行处理。重点关注的还是 else 中的逻辑,可以看到又是通过 createViewFromTag 来得到一个View,接着为这个 View 设置 LayoutParams,然后又调用了 rInflateChildren。显然这里就是一个递归调用,来不断解析xml文件中的View。
到这里重点就应该看一下 View 到底是如何生成的了,看下 createViewFromTag 方法。
ViewcreateViewFromTag(Viewparent, Stringname, Contextcontext, AttributeSetattrs,
booleanignoreThemeAttr) {
// ...try {
Viewview = tryCreateView(parent, name, context, attrs);
if (view == null) {
finalObjectlastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
returnview;
} catch (InflateExceptione) {
throwe;
} catch (ClassNotFoundExceptione) {
finalInflateExceptionie = newInflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throwie;
} catch (Exceptione) {
finalInflateExceptionie = newInflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throwie;
}
}首先通过 tryCreateView 来尝试创建 View,源码如下:
publicfinalViewtryCreateView(@NullableViewparent, @NonNullStringname,
@NonNullContextcontext,
@NonNullAttributeSetattrs) {
// 如果是一个 blink 标签,则创建一个 BlinkLayout,这个布局会没间隔 500ms 闪烁一次。if (name.equals(TAG_1995)) {
// Let's party like it's 1995!returnnewBlinkLayout(context, attrs);
}
Viewview;
if (mFactory2 != null) {
// 可以看到,这里如果 Factory2 不为空则使用 Factory2 的 Factory 来创建Viewview = mFactory2.onCreateView(parent, name, context, attrs);
} elseif (mFactory != null) {
// 如果 mFactory2 为null,但是 mFactory 不为 null,则通过Factory的Factory创建Viewview = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
// 使用私有的mPrivateFactory创建Viewview = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
returnview;
}但是,这里在没有设置 Factory2 或 Factory 的时候这两个值都是空,所有这里先不讨论这一情况。接着看 createViewFromTag 这个方法,可以看到,接下来调用了 onCreateView 来创建 View,而 onCreateView 最终调用了 createView,代码如下:
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// ...
// 从 sConstructorMap 缓存中查找 name 对应的构造方法
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
if (constructor == null) {
// 缓存中没有找到 name 对应的构造方法,则尝试获取 name 对应的Class对象
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
// 获取构造方法
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
// 将构造方法放入缓存
sConstructorMap.put(name, constructor);
} else {
// ...
}
// ...
try {
// 通过构造方法实例化 View 对象
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
} // ... 省略 catch 相关代码
}
可以看到,在createView方法中,首先会从 sConstructorMap 中查找 name对应的构造方法,如果没有找到,则获取 name对应的Class类对象,然后获取构造方法,最终,通过构造方法反射实例化出了View对象。
上一章提到的 tryCreateView 方法中首先判断了Factory2是否为空,不为空则使用 Factory2 的 onCreateView方法来创建View,如果 Factory2 为空,接着又尝试使用 Factory 来创建View。如果经过以上代码 View 依然为空,才会使用反射创建View。那么,Factory 与 Factory2 是什么?它们又是在什么时候赋值的?
在LayoutInflater源码中可以看到Factory是一个接口,且其内部只有一个onCreateView方法,源码如下:
publicinterfaceFactory {
/** * 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. */@NullableViewonCreateView(@NonNullStringname, @NonNullContextcontext,
@NonNullAttributeSetattrs);
}从onCreateView的注释中可以看出来,它是一个在LayoutInflater inflate View时的一个hook点,可以使用它来自定义xml中的标签。如果不理解这句话,可以一下BackgroundLibrary这个库。
而Factory2 则是一个继承了Factory的接口,它的代码如下:
publicinterfaceFactory2extendsFactory {
/** * 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. */@NullableViewonCreateView(@NullableViewparent, @NonNullStringname,
@NonNullContextcontext, @NonNullAttributeSetattrs);
}Factory2 继承了 Factory,且 Factory2 中新增了一个 onCreateView 方法,这个方法与Factory 的 onCreateView相比多了一个parent 的参数。因此,如果需要使用parent的话,可以使用Factory2这个接口。
Factory 与 Factory2 是在哪里被赋值的?翻下LayoutInflater 的代码,可以看到如下:
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 {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
LayoutInflater 中提供了设置 Factory 与Factory2 的方法。
那么这两个接口除了上边提到的用它来自定义xml中的标签外,还有其他用途吗?答案是确定的。我们可以使用Factory2来实现XML文件中的View替换,还可以使用Factory2实现换肤框架。
其实,在AppCompatActivity中已经默认为LayoutInflater设置了Factory2,来看AppCompatActivity的onCreate方法:
@OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState) {
finalAppCompatDelegatedelegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}这里先获取了一个AppCompatDelegate的实例,然后调用了它的 installViewFactory方法,这个方法的实现是在AppCompatDelegateImpl中,AppCompatDelegateImpl实现了Factory2接口:
classAppCompatDelegateImplextendsAppCompatDelegateimplementsMenuBuilder.Callback, LayoutInflater.Factory2 {
// ...publicvoidinstallViewFactory() {
LayoutInflaterlayoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceofAppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
} 在 installViewFactory 方法中通过LayoutInflaterCompat的 setFactory2方法为LayoutInflater设置了Factory2,即将AppCompatDelegateImpl设置到了LayoutInflater中。
class AppCompatDelegateImpl extends AppCompatDelegate
implements MenuBuilder.Callback, LayoutInflater.Factory2 {
// ...
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
String viewInflaterClassName =
a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
if ((viewInflaterClassName == null)
|| AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
// 初始化AppCompatViewInflater
mAppCompatViewInflater = new AppCompatViewInflater();
} else {
try {
// 初始化AppCompatViewInflater
Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
mAppCompatViewInflater =
(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
.newInstance();
} catch (Throwable t) {
// 初始化AppCompatViewInflater
mAppCompatViewInflater = new AppCompatViewInflater();
}
}
}
boolean inheritContext = false;
// ...
// 调用AppCompatViewInflater 的 createView
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
} AppCompatDelegateImpl 中重写了Factory2 的onCreateView方法,并调用了createView方法。而createView方法中的核心就是实例化了AppCompatViewInflater,接着调用了AppCompatViewInflater的createView方法。
AppCompatViewInflater的createView做了什么?
finalViewcreateView(Viewparent, finalStringname, @NonNullContextcontext,
@NonNullAttributeSetattrs, booleaninheritContext,
booleanreadAndroidTheme, booleanreadAppTheme, booleanwrapContext) {
finalContextoriginalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy// by using the parent's contextif (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specifiedcontext = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
Viewview = null;
// We need to 'inject' our tint aware Views in place of the standard framework versionsswitch (name) {
case"TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case"ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case"Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case"EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case"Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case"ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case"CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case"RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case"CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case"AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case"MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case"RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case"SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
case"ToggleButton":
view = createToggleButton(context, attrs);
verifyNotNull(view, name);
break;
default:
// The fallback that allows extending class to take over view inflation// for other tags. Note that we don't check that the result is not-null.// That allows the custom inflater path to fall back on the default one// later in this method.view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually// inflate it using the name so that android:theme takes effect.view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClickcheckOnClickListener(view, attrs);
}
returnview;
}
protectedAppCompatTextViewcreateTextView(Contextcontext, AttributeSetattrs) {
returnnewAppCompatTextView(context, attrs);
}
@NonNullprotectedAppCompatImageViewcreateImageView(Contextcontext, AttributeSetattrs) {
returnnewAppCompatImageView(context, attrs);
}
// ...可以看到,如果我们在xml中声明的是ImageView/TextView,则会被替换成AppCompatImageView/AppCompatTextView。也就是说AppCompactActivity通过Factory2接口帮我们实现了兼容版本的View。
参考:
- JMM与volatile关键字
- synchronized的实现原理
- synchronized等待与唤醒机制
- ReentrantLock的实现原理
- ReentrantLock等待与唤醒机制
- CAS、Unsafe类以及Automic并发包
- ThreadLocal的实现原理
- 线程池的实现原理
- Java线程中断机制
- 多线程与并发常见面试题
- Android基础知识汇总
- MVC、MVP与MVVM
- SparseArray实现原理
- ArrayMap的实现原理
- SharedPreferences
- Bitmap
- Activity的启动模式
- Fragment核心原理
- 组件化项目架构搭建
- 组件化WebView架构搭建
- 为什么 Activity.finish() 之后 10s 才 onDestroy ?
- Android系统启动流程
- InputManagerService
- WindowManagerService
- Choreographer详解
- SurfaceFlinger
- ViewRootImpl
- APP启动流程
- Activity启动流程
- PMS 安装与签名校验
- Dalvik 与 ART
- Glide实现原理
- OkHttp实现原理
- Retrofit实现原理
- RxJava实现原理
- RxJava中的线程切换与线程池
- LeakCanary实现原理
- ButterKnife实现原理
- ARouter实现原理
- Tinker实现原理
- 2. 两数相加
- 19.删除链表的倒数第 N 个结点
- 21. 合并两个有序链表
- 24. 两两交换链表中的节点
- 61. 旋转链表
- 86. 分隔链表
- 92. 反转链表 II
- 141. 环形链表
- 160. 相交链表
- 206. 反转链表
- 206 反转链表 扩展
- 234. 回文链表
- 237. 删除链表中的节点
- 445. 两数相加 II
- 面试题 02.02. 返回倒数第 k 个节点
- 面试题 02.08. 环路检测
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 22. 链表中倒数第k个节点
- 剑指 Offer 35. 复杂链表的复制
- 1. 两数之和
- 11. 盛最多水的容器
- 53. 最大子序和
- 75. 颜色分类
- 124.验证回文串
- 167. 两数之和 II - 输入有序数组 -169. 多数元素
- 189.旋转数组
- 209. 长度最小的子数组
- 283.移动0
- 303.区域和检索 - 数组不可变
- 338. 比特位计数
- 448. 找到所有数组中消失的数字
- 643.有序数组的平方
- 977. 有序数组的平方



