本文作者授权发布,链接如下: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 的布局解析过程。这样可以实现布局重用、降低布局层次以及布局懒加载等功能。