一、前言
上文【Android一杯冰美式的时间--去找setContentView】,最后因为我太困了而结束在LayoutInflater
。这篇文章还是要补足玩这一步的。
在 Android 应用中,界面是通过布局文件(通常是 XML 文件)来定义的。这些布局文件描述了界面的结构和外观,包括各种控件和它们的属性。但是,为了在应用运行时使用这些布局,我们需要将它们从 XML 文件转换成 Java 或 Kotlin 代码中的View对象。这就是 LayoutInflater
的作用所在。
如果你没用过LayoutInflater
....当我没说。
(说完了...)
如果您有任何疑问、对文章写的不满意、发现错误、想吐槽或者有更好的想法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
二、使用
看看我们平常使用LayoutInflater
的方法:
通过系统服务获取布局加载器
ini
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(resource,root,attachToRoot);
通过Activity中的getLayoutInflater()方法
ini
View view = getLayoutInflater().inflate(resource,root,attachToRoot);
通过View的静态inflate()方法
ini
View view = View.inflate(resource,root,attachToRoot);
通过LayoutInflater的from()方法
ini
View view = LayoutInflater.from(this).inflate(resource,root,attachToRoot);
三、inflate
最后你会发现"二"中这些用法间接或直接的调用了LayoutInflater
中的静态方法:
less
public static LayoutInflater from(@UiContext Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
它们使用的都是:
ini
LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
PS:系统会初始化LAYOUT_INFLATER_SERVICE
服务,AssertionError("LayoutInflater not found.")
几乎不会出现(反正我没看到过)。
然后它们都会调用LayoutInflater.inflate
,而它有三个重载函数
less
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
//会创建一个XmlResourceParser对象
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
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();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
最后方法都会指向inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
。我将展开/省略该方法的部分代码:
java
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
// ... [省略部分代码]
View result = root;
try {
//⬇️advanceToRootNode(parser)展开
//移动解析器到 XML 文档的根元素,找根布局
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//⬆️advanceToRootNode(parser)展开
final String name = parser.getName();
// <merge> 标签只能在 root 非 null 且 attachToRoot 为 true 的情况下使用
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can only be used with a valid ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 创建根视图
// 按下不表 1⃣️
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
// 如果 root 非 null,则生成与 root 匹配的布局参数
if (root != null) {
params = root.generateLayoutParams(attrs);
// 如果不附加到 root,则先设置布局参数
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// 递归地填充所有子视图
rInflateChildren(parser, temp, attrs, true);
// 如果 root 非 null 且 attachToRoot 为 true,则将解析的视图附加到 root
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 根据 attachToRoot 决定返回哪个视图
if (root == null || !attachToRoot) {
result = temp;
}
}
}
// ... [省略部分代码]
return result;
}
}
总注释中,我们可以了解到
-
@Nullable ViewGroup root:
- 这个参数指定了布局文件中顶级视图的父容器。它可以是
null
,表示没有父容器。 - 当
root
非null
时,解析出的视图可以选择性地附加到这个root
。 - 当使用
<merge>
标签时,root
不能为null
,且attachToRoot
必须为true
。
- 这个参数指定了布局文件中顶级视图的父容器。它可以是
-
boolean attachToRoot:
- 这个参数决定了解析出的视图是否应该立即附加到
root
视图组。 - 当
attachToRoot
为true
且root
非null
时,解析出的视图会被添加到root
中。 - 当
attachToRoot
为false
时,即使root
非null
,解析出的视图也不会立即添加到root
中,而是返回这个顶级视图供后续操作。
- 这个参数决定了解析出的视图是否应该立即附加到
你可能还是有点迷糊,总结性的来说:在任何我们不负责将View
添加进ViewGroup
的情况下都应该将attachToRoot
设置为false。比如RecyclerView
的onCreateViewHolder
。比如Fragment
的onCreateView
,FragmentManager
负责将 Fragment 的视图插入到容器中。如果在 onCreateView
中已经将视图附加到 root
,那么当 FragmentManager
尝试再次执行这个操作时,就会引发 IllegalStateException
,因为一个视图不能有多个父视图。
反之你就可以使用true值。
四、Factory2和Factory
在inflate
代码1⃣️中,选择性的忽略了createViewFromTag
这个方法的细节。视图如何创建出来?让我们看看最终指向的方法:
ini
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// ... [省略部分代码]
try {
//tryCreateView
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//onCreateView
view = onCreateView(context, parent, name, attrs);
} else {
//createView
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
// ... [省略部分代码]
return view;
}
}
tryCreateView
我们可以看到的是,view
是否为空,直接影响着下面流程。那有必要看看tryCreateView
的具体内容:
less
public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet 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;
}
好了,对于不太熟悉的人来说,直接懵掉。mFactory2?mFactory?先来说mFactory:
less
private Factory mFactory;
public interface Factory {
//当从 LayoutInflater 加载布局时,你可以提供一个回调(hook),在布局加载过程中被调用。你可以使用这个回调来自定义你的 XML 布局文件中可用的标签名
View onCreateView(@NonNull String name, @NonNull Context context,@NonNull AttributeSet attrs);
}
看起来mFactory
允许开发者提供自定义的逻辑来替代或增强标准的视图创建过程。官方🪝钩子!
像这样:
typescript
LayoutInflater inflater = LayoutInflater.from(context);
inflater.setFactory(new LayoutInflater.Factory() {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
// 根据 name 创建自定义视图
if (name.equals("HarmonyOSGreateAgain")) {
return new HarmonyOSView(context, attrs);
}
// 对于非自定义视图,返回 null 以使用默认行为
return null;
}
});
当然你也可以这样:
typescript
LayoutInflater inflater = LayoutInflater.from(context);
inflater.setFactory(new LayoutInflater.Factory() {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
// 根据 name 创建自定义视图
if (name.equals("TextView")) {
return new HarmonyOSTextView(context, attrs);
}
// 对于非自定义视图,返回 null 以使用默认行为
return null;
}
});
没错!你可以创建自定义 UI 组件或者改变标准组件的行为!
接着看看mFactory2
:
less
private Factory2 mFactory2;
public interface Factory2 extends Factory {
View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs);
}
细心的你发现,这玩意继承自Factory
,且多了一个parent
参数。是的,如你想的那样,它可以对创建 View 的 Parent 进行控制。这就是它的主要目的。
你已经注意到Factory
是通过setFactory
设置的,那Factory2
你也该猜到了。setFactory2
.....
那么什么是标准组件的行为呢?
1.
onCreateView
这就要看到createViewFromTag
中的onCreateView
和createView
了。onCreateView
最终指向createView
,我们看看createView
:
less
//反射构造View
public final View createView(@NonNull Context viewContext, @NonNull String name,@Nullable String prefix, @Nullable AttributeSet attrs) throws ClassNotFoundException, InflateException {
// 确保参数不为空
Objects.requireNonNull(viewContext);
Objects.requireNonNull(name);
// 尝试从缓存中获取视图的构造函数
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// 如果构造函数不在缓存中,尝试加载视图类
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 {
// 对缓存的构造函数应用过滤器
// ... [省略过滤器逻辑]
}
// 设置构造函数参数并创建视图实例
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
//反射构造
final View view = constructor.newInstance(args);
// 特殊处理 ViewStub
// ... [省略 ViewStub 处理逻辑]
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
} catch (NoSuchMethodException | ClassCastException | ClassNotFoundException | Exception e) {
// 处理各种异常
// ... [省略异常处理逻辑]
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
简单吧~总结下来就两个:
- 从缓存集合中获取当前View对应的构造方法,没有则创建,并存入缓存。
- 反射构造方法,创建对应的View对象
IllegalStateException
当我们兴致勃勃的去准备大改特改的时候,你会发现:
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val inflater: LayoutInflater = LayoutInflater.from(this)
//使用LayoutInflater.Factory
inflater.factory = LayoutInflater.Factory { name, context, attrs -> null }
//使用LayoutInflater.Factory2
inflater.factory2 = object : LayoutInflater.Factory2 {
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
return XXX
}
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
return XXXX
}
}
}
}
两个设置方法都会崩溃~
csharp
Caused by: java.lang.IllegalStateException: A factory has already been set on this LayoutInflater
at android.view.LayoutInflater.setFactory(LayoutInflater.java:317)
//和
Caused by: java.lang.IllegalStateException: A factory has already been set on this LayoutInflater
at android.view.LayoutInflater.setFactory2(LayoutInflater.java:375)
错误来自setFactory2/setFactory
:(PS:setFactory2/setFactory
基本一致)
ini
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);
}
}
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);
}
}
我们可以看出,setFactory2/setFactory
均只能调用一次。但是明明我们只调用了一次?为什么会抛出异常呢?
3.
AppCompatDelegate.createView
我们四处寻找setFactory2/setFactory
的使用者,找到了AppCompatDelegate
以及它的实现类AppCompatDelegateImpl
,眼熟吧!【Android一杯冰美式的时间--去找setContentView】提到的!
最终我们可以在实现类中找到
java
@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");
}
}
}
至于为什么没有setFactory
的调用,你也找不到呢,因为被弃用了,而setFactory2
也会 mFactory = mFactory2 = factory
。相信细心的你也发现~
我们继续对setFactory2,进行跟踪,那么我们肯定需要寻找,它的实现类。最后我们可以定位到AppCompatDelegateImpl
的onCreateView
->createView
方法,我们和LayoutInflater中的createView对比就知道
ini
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
// 检查是否已经有一个 AppCompatViewInflater 实例,如果没有,则创建一个
if (mAppCompatViewInflater == null) {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
String viewInflaterClassName = a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
if (viewInflaterClassName == null) {
// 如果在主题中没有指定自定义视图创建器,则使用默认的 AppCompatViewInflater
mAppCompatViewInflater = new AppCompatViewInflater();
} else {
try {
// 尝试通过反射加载并实例化自定义的 AppCompatViewInflater
Class<?> viewInflaterClass = mContext.getClassLoader().loadClass(viewInflaterClassName);
mAppCompatViewInflater = (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor().newInstance();
} catch (Throwable t) {
// 如果反射失败,回退到默认的 AppCompatViewInflater
Log.i(TAG, "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", t);
mAppCompatViewInflater = new AppCompatViewInflater();
}
}
}
// 标记是否应该继承上下文
boolean inheritContext = false;
if (IS_PRE_LOLLIPOP) {
// 对于 Android Lollipop 之前的版本,检查是否需要继承上下文
if (mLayoutIncludeDetector == null) {
mLayoutIncludeDetector = new LayoutIncludeDetector();
}
if (mLayoutIncludeDetector.detect(attrs)) {
inheritContext = true;
} else {
inheritContext = (attrs instanceof XmlPullParser) ? ((XmlPullParser) attrs).getDepth() > 1 : shouldInheritContext((ViewParent) parent);
}
}
// 使用 AppCompatViewInflater 创建视图
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, // 仅在 Lollipop 之前的版本中读取 android:theme
true, // 始终读取 app:theme,用于遗留原因
VectorEnabledTintResources.shouldBeUsed() // 根据配置决定是否使用着色资源
);
}
显然核心代码在AppCompatViewInflater.createView
中。
4.
AppCompatViewInflater.createView
less
@Nullable
public final View createView(@Nullable View parent, @NonNull final String name,
@NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// 根据需要调整上下文
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// 根据标签名创建 AppCompat 支持的视图
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
// ...其他视图创建逻辑
default:
// 尝试使用自定义方法创建视图
view = createView(context, name, attrs);
}
// 如果原始上下文和调整后的上下文不同,尝试重新创建视图
if (view == null && originalContext != context) {
view = createViewFromTag(context, name, attrs);
}
// 检查视图的 onClick 属性和无障碍属性
if (view != null) {
checkOnClickListener(view, attrs);
backportAccessibilityAttributes(context, view, attrs);
}
return view;
}
在switch (name)中,返回的都是AppCompatXXX。因此,我们可以确认,默认用于确保在旧版 Android
系统上,应用也能够使用 Material Design
样式的视图,同时保持向后兼容性。也就是统一 Material Design
样式。而它最后指向了AppCompatViewInflater
好了现在你已经学会使用Factory了。
值得注意的是,一般而言你需要保留AppCompatViewInflater
做出的兼容操作。所以你需要如此做:
typescript
LayoutInflater.from(this).setFactory2(new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
// 调用 AppCompatDelegate 的createView方法
getDelegate().createView(parent, name, context, attrs);
// 自由发挥
return XXX;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return XXX;
}
});
显然使用setFactory
需要在加载布局前,也就是调用inflate方法之前。
五、用途
你可能已经意识到了,setFactory
的用途,通过 LayoutInflater 创建 View 时候的一个回调,可以通过 LayoutInflater.Factory 来改造或定制创建 View 的过程。比如样式替换,比如自定义的View等等等。这里我们展示接管View的背景绘制,你可以扩展成"无需自定义View,直接添加属性便可以实现shape、selector的效果"
以下算是一个通用操作了,也是模仿AppCompatViewInflater的流程:
kotlin
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.LayoutInflaterCompat
object BackgroundLibrary {
fun inject(context: Context?): LayoutInflater? {
val inflater: LayoutInflater? = if (context is Activity) {
context.layoutInflater
} else {
LayoutInflater.from(context)
}
if (inflater == null) {
return null
}
if (inflater.factory2 == null) {
val factory = setDelegateFactory(context!!)
inflater.factory2 = factory
} else if (inflater.factory2 !is BackgroundFactory) {
forceSetFactory2(inflater)
}
return inflater
}
/**
* 注入自定义 LayoutInflater 工厂的主方法
* 如果因为其他库已经设置了factory,可以使用该方法去进行inject,在其他库的setFactory后面调用即可
*/
fun inject2(context: Context?): LayoutInflater? {
// 根据 Context 类型获取 LayoutInflater 实例
val inflater: LayoutInflater? = if (context is Activity) {
context.layoutInflater
} else {
LayoutInflater.from(context)
}
if (inflater == null) {
return null
}
// 强制设置自定义工厂
forceSetFactory2(inflater)
return inflater
}
// 创建并配置 BackgroundFactory 实例
private fun setDelegateFactory(context: Context): BackgroundFactory {
val factory = BackgroundFactory()
if (context is AppCompatActivity) {
// 如果是 AppCompatActivity 实例,使用其委托创建视图
val delegate = context.delegate
factory.setInterceptFactory { name, context, attrs ->
delegate.createView(null, name, context, attrs)
}
}
return factory
}
// 通过反射技术强制为 LayoutInflater 设置自定义工厂
@SuppressLint("DiscouragedPrivateApi")
private fun forceSetFactory2(inflater: LayoutInflater) {
val compatClass = LayoutInflaterCompat::class.java
val inflaterClass = LayoutInflater::class.java
try {
// 访问私有字段并修改其值,以便可以设置自定义工厂
val sCheckedField = compatClass.getDeclaredField("sCheckedField").apply {
isAccessible = true
setBoolean(compatClass, false)
}
val mFactory = inflaterClass.getDeclaredField("mFactory").apply {
isAccessible = true
}
val mFactory2 = inflaterClass.getDeclaredField("mFactory2").apply {
isAccessible = true
}
// 创建 BackgroundFactory 实例
val factory = BackgroundFactory()
if (inflater.factory2 != null) {
factory.setInterceptFactory2(inflater.factory2)
} else if (inflater.factory != null) {
factory.setInterceptFactory(inflater.factory)
}
// 设置工厂到 LayoutInflater 的 mFactory 和 mFactory2 字段
mFactory2[inflater] = factory
mFactory[inflater] = factory
} catch (e: IllegalAccessException) {
// 处理反射访问异常
e.printStackTrace()
} catch (e: NoSuchFieldException) {
// 处理反射访问异常
e.printStackTrace()
}
}
}
kotlin
class BackgroundFactory : LayoutInflater.Factory2 {
// 已经存在的工厂和工厂2的引用
private var mViewCreateFactory: LayoutInflater.Factory? = null
private var mViewCreateFactory2: LayoutInflater.Factory2? = null
// 用于创建视图的方法
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
// 检查是否为特定前缀的视图,如果是,则不处理
if (name.startsWith("com.fuck.harmonyos.view")) {
return null
}
var view: View? = null
// 首先尝试使用已经存在的工厂创建视图
if (mViewCreateFactory2 != null) {
view = mViewCreateFactory2!!.onCreateView(name, context, attrs)
if (view == null) {
view = mViewCreateFactory2!!.onCreateView(null, name, context, attrs)
}
} else if (mViewCreateFactory != null) {
view = mViewCreateFactory!!.onCreateView(name, context, attrs)
}
// 对创建的视图应用自定义背景处理
return setViewBackground(name, context, attrs, view)
}
// 设置拦截的工厂
fun setInterceptFactory(factory: LayoutInflater.Factory) {
mViewCreateFactory = factory
}
fun setInterceptFactory2(factory: LayoutInflater.Factory2) {
mViewCreateFactory2 = factory
}
// Factory2 接口的另一个方法实现
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
return onCreateView(name, context, attrs)
}
// 伴生对象,包含静态方法和属性
companion object {
// ... [省略静态方法和属性的注释]
}
}
一个BackgroundFactory,BackgroundLibrary。就可以随意组合了,具体实现可以在BackgroundFactory。慢慢琢磨。
如果是一个懒狗,肯定不乐意在每个Activity中,去添加inject的操作。所以可以直接如此做:
kotlin
class FuckApplication : Application() {
init {
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
BackgroundLibrary.inject(activity)
//BackgroundLibrary.inject2(activity)
}
// ... [省略部分代码]
})
}
}
六、 结尾
部分代码可以在这里看到
感谢仓库BackgroundLibrary,该库基于LayoutInflater.Factory原理完成。
如果您有任何疑问、对文章写的不满意、发现错误、想吐槽或者有更好的想法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏