本文作者授权发布,链接如下:https://juejin.im/post/6886052422260228103。
在Android UI开发中,经常需要使用LayoutInflater类,它的基本作用是将XML布局文件解析成View / View树。除了基本的布局解析功能,LayoutInflater还可以用于实现动态换肤、视图转换、属性转换等需求。本文将详细解读LayoutInflater的源码,并提供应试建议。如果能帮到你,请务必点赞加关注,这对我真的非常重要。
目录:
1. 获取LayoutInflater对象
要获得LayoutInflater的实例,有以下几种方法:
1. View.inflate(...):
```java
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
```
2. Activity#getLayoutInflater():
```java
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
```
3. PhoneWindow#getLayoutInflater():
```java
private LayoutInflater mLayoutInflater;
public PhoneWindow(Context context) {super(context);mLayoutInflater = LayoutInflater.from(context);}
```
以下是重构后的代码:
```java
public LayoutInflater getLayoutInflater() {
return mLayoutInflater;
}
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;
}
```
其中,`from()`方法通过调用`context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)`来获取`LayoutInflater`实例。如果获取到的实例为null,则抛出异常。最后返回该实例。
在 Android 系统中,ContextImpl 类提供了获取系统服务的方法。以下是重构后的内容:
1. 在静态代码块中注册 Context.LAYOUT_INFLATER_SERVICE 与服务获取器。关注点:CachedServiceFetcher;关注点:PhoneLayoutInflater。首先,我们需要注册服务名称(name)和相应的服务获取器(ServiceFetcher)。在静态代码块中执行以下操作:
```java
static {
registerService("layoutInflater", "android.view.LayoutInflater", new CachedServiceFetcher() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
// 注意:getOuterContext(),参数使用的是 ContextImpl 的代理对象,一般是 Activity
return new PhoneLayoutInflater(ctx.getOuterContext());
}
});
}
```
2. 根据 name 获取服务对象。我们可以使用 `getSystemService` 方法来实现这个功能。该方法接收两个参数:一个是上下文(ContextImpl),另一个是服务名称(String)。方法内部通过 `SYSTEM_SERVICE_FETCHERS` 映射来获取服务对象。如果找到了对应的服务获取器,就使用它来创建服务对象;否则,返回 null。
```java
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
```
3. 实现服务获取器接口 `ServiceFetcher` 并创建对象。这个接口只有一个抽象方法 `getService`,用于创建服务对象。具体创建哪个服务对象需要根据服务名称和上下文来决定。在这个例子中,我们使用了 `CachedServiceFetcher` 作为具体的服务获取器,它根据服务名称创建 `PhoneLayoutInflater` 对象。
```java
private static void registerService(String serviceName, Class serviceClass, ServiceFetcher serviceFetcher) {
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
```
4. 最后,我们可以看到 ContextImpl 内部通过 SystemServiceRegistry 来获取服务对象。整个逻辑并不复杂:1、静态代码块注册了 name-ServiceFetcher 的映射;2、根据 name 获得 ServiceFetcher;3、ServiceFetcher 创建对象。
getSystemService()方法是Android系统提供的一个用于获取系统服务的API。根据不同的服务类型,getSystemService()方法返回不同类型的服务对象。例如,对于LayoutInflater来说,getSystemService()方法返回的是一个ServiceFetcher子类,最终获得的服务对象为PhoneLayoutInflater。
在Android系统中,有多种类型的服务获取器,如CachedServiceFetcher、StaticServiceFetcher和StaticApplicationContextServiceFetcher等。这些服务获取器分别具有不同的作用域和描述。
1. CachedServiceFetcher:单例范围,主要用于缓存已获取的服务对象,以提高性能。例如,ContextImpl域中的服务获取器就是CachedServiceFetcher的实例。
2. StaticServiceFetcher:进程域,主要用于在多个进程之间共享服务对象。例如,InputManager、JobScheduler等都是使用StaticServiceFetcher创建的服务对象。
3. StaticApplicationContextServiceFetcher:进程域,主要用于在多个进程之间共享ApplicationContext创建的服务对象。例如,ConnectivityManager就是一个使用StaticApplicationContextServiceFetcher创建的服务对象。
下面通过一个例子来说明如何使用getSystemService()方法获取LayoutInflater服务对象:
```java
// 获取LayoutInflater服务对象
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
```
需要注意的是,这个例子中的关键代码非常隐蔽,需要仔细观察:
```java
return new PhoneLayoutInflater(ctx.getOuterContext());
```
这句代码的作用是创建一个PhoneLayoutInflater实例,并将当前上下文的外部上下文传递给它。这样可以确保在多线程环境下正确地获取到服务对象。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 1. 解析预编译的布局View
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
// 2. 构造 XmlPull 解析器
XmlResourceParser parser = res.getLayout(resource);
try {
// 3. 执行解析
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
private View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
// 结果变量
View result = root;
// 最外层的标签
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
// 异常处理
if (root == null || !attachToRoot) {
throw new InflateException("can be used only with a valid" + "ViewGroup root and attachToRoot=true");
}
//递归执行解析
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 创建最外层 View
LayoutInflater inflater = LayoutInflater.from(context);
result = createViewFromTag(inflater, parser);
}
return result;
下面是根据你的需求重构的代码:
```java
final View createViewFromTag(ViewGroup root, String name, Context inflaterContext, AttributeSet attrs) {
View temp = null;
ViewGroup.LayoutParams params = null;
if (root != null) {
// 4.2 创建匹配的LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 4.3 如果 attachToRoot 为 false,设置LayoutParams
temp.setLayoutParams(params);
}
}
// 以 temp 为 root,递归执行解析rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
// attachToRoot为true,addView()
root.addView(temp, params);
} else if (root == null || !attachToRoot) {
return temp;
}
}
```
.2 方法重构:
```java
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
while (parser != null && parser.getEventType() != XmlPullParser.END_DOCUMENT) {
String name = parser.getName();
int depth = parser.getDepth();
if (TAG_INCLUDE.equals(name)) {
if (depth == 0) {
throw new InflateException("Cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("Must be the root element");
} else {
// Create View
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// Recursively inflate children
rInflateChildren(parser, view, attrs, true);
// Add to view tree
viewGroup.addView(view, params);
}
parser.next();
}
}
```
5. 递归执行解析的方法:
```java
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
```
createViewFromTag()方法的作用是从给定的父View中,根据指定的名称和属性来创建一个View。具体实现如下:
1. 首先,如果需要忽略主题属性,不使用ContextThemeWrapper包装上下文,直接获取类型化数组(TypedArray)并从中获取主题资源ID。如果主题资源ID不为0,则使用ContextThemeWrapper包装上下文。
2. 接着,判断是否有自定义的Factory实例,如果有,则使用该Factory实例创建View;如果没有,则使用默认的Factory实例创建View。这样可以实现拦截View的创建过程。
3. 最后,再次判断是否有自定义的PrivateFactory实例,如果有且之前没有通过Factory创建到View,则使用PrivateFactory实例创建View。
以下是重构后的代码:
```java
public View createViewFromTag(ViewGroup parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
// 应用 ContextThemeWrapper 以支持 android:theme
if (!ignoreThemeAttr) {
TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
// 先使用 Factory2 / Factory 实例化 View,相当于拦截View view
if (mFactory2 != null) {
View view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// 使用 mPrivateFactory 实例化 View,相当于拦截
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
```
在这段 Java 代码中,我们定义了一个名为 `createView` 的方法,该方法用于根据给定的名称、前缀和属性集创建一个 View 对象。如果视图为 null,且名称中没有 '.view',则调用 `onCreateView(parent, name, attrs)` 方法;否则,调用 `createView(name, null, attrs)` 方法。为了提高性能,我们使用了一个缓存来存储构造器方法签名 (`mConstructorSignature`),并在创建新的 View 构造器时进行验证。
以下是代码重构后的段落结构:
1. 如果视图为 null 且名称中没有 '.view',则调用 `onCreateView(parent, name, attrs)` 方法:
```java
if (view == null && -1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
}
```
2. 如果视图不为 null 或者名称中有 '.view',则调用 `createView(name, prefix, attrs)` 方法。首先尝试从缓存中获取构造器:
```java
if (-1 != name.indexOf('.')) {
// ...
// 在此处省略其他代码
// ...
Constructor constructor = sConstructorMap.get(name); // 从缓存中获取构造器
if (constructor != null && verifyClassLoader(constructor)) { // 如果构造器存在且经过验证
view = createView(name, null, attrs); // 直接调用此方法创建 View
return view;
} else {
constructor = null; // 没有找到合适的构造器
}
Class clazz = null; // 将 Class 类型的变量初始化为 null
// ...
// 在此处省略其他代码
// ...
constructor = clazz.getConstructor(mConstructorSignature); // 创建新的构造器对象
constructor.setAccessible(true); // 设置构造器的可访问性
// ...
// 在此处省略其他代码
// ...
MapsConstructorMap.put(name, constructor); // 将新创建的构造器存入缓存
view = createView(name, null, attrs); // 通过新创建的构造器创建 View 并返回
return view;
} else {
view = onCreateView(parent, name, attrs); // 直接调用 onCreateView 方法创建 View 并返回
return view;
}
```
您好!您的问题是关于 View 实例化的,您可以使用 Factory2 接口拦截实例化 View 对象的步骤。实例化 View 的优先顺序为:Factory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater。使用反射实例化 View 对象,同时构造器对象做了缓存。
Factory2Factory2LayoutInflater.java
方法1: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);
}
}
方法2 @hidepublic void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}
现在,我们来看源码中哪里调用这两个方法:
4.1 setFactory2()
在 AppCompatActivity & AppCompatDialog 中,相关源码简化如下:
AppCompatDialog.java
@Overrideprotected void onCreate(Bundle savedInstanceState) {
设置 Factory2getDelegate().installViewFactory();
super.onCreate(savedInstanceState);
getDelegate().onCreate(savedInstanceState);
}
AppCompatActivity.java
```java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
Factory2delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
//夜间主题相关
if (delegate.applyDayNight() && mThemeId != 0) {
if (Build.VERSION.SDK_INT >= 23) {
onApplyThemeResource(getTheme(), mThemeId, false);
} else {
setTheme(mThemeId);
}
}
super.onCreate(savedInstanceState);
}
```
AppCompatDelegateImpl.java
```java
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
//关注点:设置 Factory2 = this(AppCompatDelegateImpl)
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.java
重构后的代码如下:
```java
public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
inflater.setFactory2(factory);
if (Build.VERSION.SDK_INT < 21) {
final LayoutInflater.Factory f = inflater.getFactory();
if (f instanceof LayoutInflater.Factory2) {
forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
} else {
forceSetFactory2(inflater, factory);
}
}
}
static class AppCompatDelegateImpl extends AppCompatDelegate implements MenuBuilder.Callback, LayoutInflater.Factory2 {
private AppCompatViewInflater mAppCompatViewInflater;
@Override
public 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) {
mAppCompatViewInflater = new AppCompatViewInflater();
}
return mAppCompatViewInflater.createView(parent, name, context, attrs);
}
}
```
在这段代码中,我们首先定义了一个名为 `setFactory2` 的静态方法,该方法接受一个 `LayoutInflater` 对象和一个实现了 `LayoutInflater.Factory2` 接口的工厂对象作为参数。然后,我们根据 Android SDK 版本的不同来设置工厂对象。如果 SDK 版本小于 21,我们需要检查 `LayoutInflater` 的工厂对象是否实现了 `LayoutInflater.Factory2` 接口,如果是,则使用强制类型转换将其转换为 `LayoutInflater.Factory2`,否则直接使用传入的工厂对象。最后,我们将工厂对象设置到 `LayoutInflater` 中。
AppCompatViewInflater.java:
```java
final View createView(Context context, String name, AttributeSet attrs) {
if (name.equals("TextView")) {
return createTextView(context, attrs);
} else {
return createView(context, name, attrs);
}
}
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
```
Activity.java:
```java
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
final void attach(Context context, ActivityThread aThread, ...) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
// Set the private factory used by theLayoutInflater
mWindow.getLayoutInflater().setPrivateFactory(this);
}
```
这里设置的 `mWindow.getLayoutInflater().setPrivateFactory(this)` 实际上就是将 Activity 作为 Factory2。
在 Android 开发中,我们经常需要自定义 Fragment 的布局。为了实现这个功能,我们需要继承 `ContextThemeWrapper` 类并实现 `LayoutInflater.Factory2` 接口。下面是一个简单的示例:
```java
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2 {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)) {
return onCreateView(name, context, attrs);
}
return mFragments.onCreateView(parent, name, context, attrs);
}
}
```
在这个示例中,我们重写了 `onCreateView()` 方法,用于创建自定义 Fragment 的布局。当传入的布局名称不是 "fragment" 时,我们会调用父类的 `onCreateView()` 方法来创建普通的布局。否则,我们会调用 `mFragments.onCreateView()` 方法来创建自定义的布局。
接下来,我们需要实现 `LayoutInflater.Factory2` 接口。这个接口有一个方法:`createView(String name, Context context, AttributeSet attrs)`,用于创建指定名称和属性的视图。在这个方法中,我们需要根据传入的布局名称和属性来解析布局文件,并返回对应的视图对象。
```java
@Override
public View createView(String name, Context context, AttributeSet attrs) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup rootView = (ViewGroup) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
int layoutId = getLayoutId(name, context, attrs);
ViewGroup view = (ViewGroup) inflater.inflate(layoutId, rootView, false);
return view;
}
```
在这个方法中,我们首先通过 `context.getSystemService()` 方法获取到 `LayoutInflater` 对象和根视图容器。然后,我们根据布局名称、上下文和属性来获取布局资源 ID。最后,我们使用 `LayoutInflater` 对象的 `inflate()` 方法来解析布局文件,并返回对应的视图对象。
总结一下,通过实现 `LayoutInflater.Factory2` 接口,我们可以自定义 Fragment 的布局解析过程。这样可以实现布局重用、降低布局层次以及布局懒加载等功能。