LayoutInflater 将布局文件(XML)实例化为一个 View 对象。
通常我们会通过 Activity#getLayoutInflater()
或者是 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
来获取一个标准的与当前运行的 Context 相关联的 LayoutInflater 实例。
我们以 Activity#setContentView(@LayoutRes int layoutResID)
为例来看一下 LayoutInflater 的工作流程。
源码:Android-29、AndroidX
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
1 AppCompatActivity#setContentView 流程
AppCompatActivity 是 AndroidX 兼容包下的 Activity 的实现基类
//#AppCompatActivity
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
可以看到内部通过代理来进行设置,下面来看代理的实现 getDelegate()
////#AppCompatActivity
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
@NonNull
public static AppCompatDelegate create(@NonNull Activity activity,
@Nullable AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, callback);
}
通过 getDelegate()
方法创建了代理的实现类 AppCompatDelegateImpl
,下面我们来看这个代理实现类中的 setContentView
方法:
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
可以看见,AppCompatActivity#setContentView
内部是通过 LayoutInflater.from(mContext).inflate(resId, contentParent)
来加载布局的
2 LayoutInflater.from(mContext) 流程
在来看 from(mContext)
方法之前,我们先明确 mContext 的类型。
2.1 mContext 的类型
往上翻可以看到我们在创建代理对象的同时,将 Activity 作为参数传入,也就是说 mContext 的类型是 AppCompatActivity 也就是 ContextThemeWrapper 类型。
这里我们贴上一张 Context 相关的背景知识:
2.2 LayoutInflater.from(mContext)
/**
* Obtains the LayoutInflater from the given 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;
}
可以发现我们是通过 context.getSystemService
来获取 LayoutInflater “服务”。
之前我们已经明确了 mContext 的类型是 ContextThemeWrapper
类型,我们来看看它的 from 方法:
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
这个的 getBaseContext 获取到的是 mBase 这个属性,它的类型是 ContextImpl,mBase 的赋值源于 Activity 在创建后的调用的 attach 方法,这里就不再展开了。
然后让我们来看看 ContextImpl 的 getSystemService
方法:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
到这里看上去是真正要获取服务的地方了,我们根据 ContextImpl 对象本身和服务名称 从 SystemServiceRegistry 中获取服务,我们可以把它理解为注册表:
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
跟进到它的 getSystemService 方法中可以看到:
- 从 SYSTEM_SERVICE_FETCHERS 中获取 ServiceFetcher
- 通过 ServiceFetcher 获取服务
服务在什么时候注册?
在 SystemServiceRegistry 的静态代码块中,对服务进行了注册:
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
可以看到最终我们获取到的 LayoutInflater 类型是 PhoneLayoutInflater 类型。
在 ContextImpl 类中有一个 mServiceCache 属性,在声明的时候已经初始化:
//ContextImpl
// The system service cache for the system services that are cached per-ContextImpl.
@UnsupportedAppUsage
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
由此可见,在 ContextImpl 创建后,服务就会进行注册,根据注释可知,每一个 ContextImpl 对象都有自己的服务缓存。
ServiceFetcher如何获取服务?
我们在回过头来看服务获取的具体逻辑(我删除了部分代码):
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
final int[] gates = ctx.mServiceInitializationStateArray;
for (;;) {
boolean doInitialize = false;
synchronized (cache) {
// Return it if we already have a cached instance.
//①
T service = (T) cache[mCacheIndex];
if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
return service;
}
if (doInitialize) {
// Only the first thread gets here.
T service = null;
try {
//②
service = createService(ctx);
newState = ContextImpl.STATE_READY;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
} finally {
synchronized (cache) {
③
cache[mCacheIndex] = service;
gates[mCacheIndex] = newState;
cache.notifyAll();
}
}
return service;
}
}
}
- 查看 ContextImpl 的缓存,缓存命中则直接返回服务
- 缓存不存在的话则通过 createService 方法创建服务
- 最后将服务缓存在 ContextImpl 中
2.3 PhoneLayoutInflater#cloneInContext
在上面 ContextThemeWrapper 获取服务的代码中 mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
我们在获取 PhoneLayoutInflater 之后,还调用了 cloneInContext 方法,名字听上去是克隆一个对象。
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
protected LayoutInflater(LayoutInflater original, Context newContext) {
mContext = newContext;
mFactory = original.mFactory;
mFactory2 = original.mFactory2;
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
initPrecompiledViews();
}
由此可见我们使用了一个新的 Context 替换了原先 LayoutInflater 中的 mContext 属性,根据上面的代码可知,我们在使用 ContextImpl 创建了 PhoneLayoutInflater 之后,将其中的 mContext 替换为 ContextThemeWrapper。
3 LayoutInflater#inflate
LayoutInflater.from(mContext).inflate(resId, contentParent);
由上面分析可知,在获取到 PhoneLayoutInflater 对象后,接着调用它的 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();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
最终调用的 inflate 方法接收三个参数:
- @LayoutRes int resource 这个参数就是我们 setContentView 方法传入的 XML 资源文件
- @Nullable ViewGroup root 这是一个可选的父布局容器
- boolean attachToRoot ,这个参数决定是否将从 XML 加载的 View 对象添加进 root 中
根据 setContentView 的调用可知,我们传入的布局文件最后会被添加进 id 为 Content 的容器中
在 inflate 方法中我们创建了当前 XML 资源文件的解析器,并将它传入重载的 inflate 房中:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
View result = root;
try {
//① merge 标签判断
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
...
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// ②创建 XMl 文件中顶层标签中标记的 View 对象
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// ③指定 LayoutParams
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);
}
}
//④递归的将子视图填充到 temp 中
context.rInflateChildren(parser, temp, attrs, true);
//⑤将根据 XML 实例化的 View 添加到 root 中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//直接返回 temp 对象
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
....
}
return result;
}
}
这里的方法有点长,我同样省略一些代码,让我们的视线集中在布局的解析上。
3.1 merge 标签
我们判断 XMl 的根标签是不是 merge,如果是 merge 标记的话在直接调用递归填充方法 rInflater
,这里我们先不看对 merge 标签的处理。
3.2 LayoutInflater#createViewFromTag
如果不是 merge 标签的话,我们将调用 createViewFromTag
方法来创建 View 对象:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//...
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = 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;
}
}
return view;
} catch (InflateException e) {
//...
}
}
这里也是分为三步:
- 尝试调用
tryCreateView
方法创建 View - 如果不是自定义 View 则调用 onCreateView 方法
- 如果是自定义 View 则调用 createView
3.2.1 LayoutInflater#tryCreateView
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
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);
}
return view;
}
- 如果 XML 的标记是 blink(TAG_1995) 的话,会创建一个 BlinkLayout 类型的对象,它会每隔 500ms 重绘一次,形成闪烁的效果。
- 尝试通过 mFactory2 对象的 onCreateView 来创建 View 对象
- 尝试通过 mFactory 对象的 onCreateView 来创建 View 对象
- 尝试通过 mPrivateFactory 对象的 onCreateView 来创建 View 对象
mFactory2、mPrivateFactory 对象都是 Factory2 类型,而 mFactory 对象是 Factory 类型,Factory2 是对 Factory 接口的升级,Factory2 继承了 Factory 接口,并且多了一个 View onCreateView(View parent, String name,Context context, @NonNull AttributeSet attrs);
相比较于 Factory 的 onCreateView 而言多了一个 View 类型的 parent 参数。
稍后我们再来看这几个属性是在何时被设置的,让我们回到 createViewFromTag 方法
3.2.2 LayoutInflater#createView
如果我们没有设置 mFactory2 这些属性,那么 tryOnCreateView 这个方法返回的 View 对象就是 null,之后我们会根据 XML 标记是否包含 "." 来决定调用不同的方法。
如果标签不包含 "." ,说明这是 Android 系统提供的 View 例如:TextView,ImageView...,这时我们会调用 onCreateView 方法,这个方法会调用一些列的重载方法,最后会调用 createView(String name, String prefix, AttributeSet attrs)
方法:
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Context context = (Context) mConstructorArgs[0];
if (context == null) {
context = mContext;
}
return createView(context, name, prefix, attrs);
}
在 onCreateView 方法当中,我们会调用 createView 方法,并且限定了 prefix 这个入参为 "android.view",紧接着我们会调用最后重载的 createView 方法:
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable 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 {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
...
}
try {
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;
}
}
...
}
我省略一些异常捕获和日志代码,可以看到我们会尝试从缓存中获取当前 View 的构造函数,如果命中缓存的话,则直接通过反射创建 View 对象并返回,否则我们会通过入参 name 和 prefix 来拼接 View 的全路径类名,然后在通过反射获取它的构造函数,放入缓存之后在创建 View 返回。
到这里我们可以说 LayoutInflater 是通过反射的方式将 XML 实例化成一个 View 对象,这么说没错,但是之前我们有提到过,在反射创建 View 对象之前,会经过 mFactory 等对象的处理,接下来让我们肯看这些“工厂“是什么时候设置的。
4 LayoutInflater#setFactory2
之前我们分析的是 AppCompatActivity#setContentView 流程:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
在 setContentView 之前我们调用了父类的 onCreate 方法:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
这里可以看到我们调用了 delegate 对象的 installViewFactory 方法:
//AppCompatDelegateImpl
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
其中 LayoutInflaterCompat.setFactory2(layoutInflater, this);
对 mFactory 进行了设置:
//LayoutInflaterCompat
class AppCompatDelegateImpl extends AppCompatDelegate
implements MenuBuilder.Callback, LayoutInflater.Factory2{
//......
}
public static void setFactory2(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
inflater.setFactory2(factory);
//省略了兼容性代码......
}
AppCompatDelegateImpl 实现了 Factory2 接口,我们通过 setFactory2 方法将 this 赋值给 mFactory 和 mFactory2 对象,下面我们来看 Factory2 接口在 AppCompatDelegateImpl 中的实现:
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
//省略部分代码......
mAppCompatViewInflater = new AppCompatViewInflater();
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed()
);
}
可以看到在 createView 方法中,我们构建了一个 AppCompatViewInflater 对象,将创建 View 的操作交给了它,接着我们看这个对象的 createView 方法:
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
// 省略了后续代码...
}
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
- 可以看见,为了能够兼容,使得 android 5.0 之前的版本能够使用矢量图功能,我们会对originalContext 进行包装,通过 TintContextWrapper 的
wrapper
方法将它包装成 TintContextWrapper 类型 - 通过字符串匹配,直接使用 new 对象的方式,创建相对应的 View,节省了反射带来的开销
由此可见,我们虽然我们在 XML 中声明的是 TextView 类型,但是为了向前兼容,系统实际创建的是 AppCompatTextView 类型;另外通过 View#getContext 方法获取到的类型也不一定是 Activity 类型,也有可能是 TintContextWrapper 类型。
5 LayoutInflater#setPrivateFactory
经过上面的流程分析,我们已经知道 mFactory 和 mFactory2 属性是什么时候被赋值的,那么 mPrivateFactory 又是什么时候被赋值的呢?
/**
* @hide for use by framework
*/
@UnsupportedAppUsage
public void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}
我们可以通过 LayoutInflater#setPrivateFactory
方法设置 mPrivateFactory,注释中说这是个隐藏方法,被 framework 层使用,这里给大家一个看源码的网站,就是谷歌最近刚开源的 Code Serach,我们通过这个网站来查看一下
setPrivateFactory 方法的引用。
如图,我们在 Activity attach 方法调用的时候通过 mWindow.getLayoutInflater().setPrivateFactory(this);
对 mPrivateFactory 进行设置,并且设置的值是 this,说明我们 Activity 也实现了 Factory2 接口。
下面我们来看 Factory2 接口在 Activity 中的实现:
public View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
if (!"fragment".equals(name)) {
return onCreateView(name, context, attrs);
}
return mFragments.onCreateView(parent, name, context, attrs);
}
可以看到这里对 XML 中的 fragment 标记进行了处理,FragmentController#onCreateView 方法会对 Fragment 进行实例化,并调用 Fragment 实现的 onCreateView 方法来返回 View 对象。
到此为止我们就知道了 XML 中的 fragment 标签是如何被处理的。
6. final
Android 系统通过给 LayoutInflater 设置工厂的方式,自己决定 View 的实例化,以此来实现向前兼容,利用 setFactory 方法我们还可以做到很多事情,比如全局字体的替换,给特定的 View 设置特定的背景...
同时我也只对最基础的流程进行分析,里面对 merge、include、viewStub 等标签的处理并没有展开,其实这些标签也只是递归的进行 View 的创建并添加进容器而已。
参考链接:
- https://juejin.im/post/5dd499a6f265da0bf21126cc
- https://blog.csdn.net/mq2553299/article/details/99737681