本文主要分析FragmentFactory
编写前后对比,以及简单的FragmentFactory
使用
简述
Fragment constructor injection is now -and has been for a while- supported in Android thanks to FragmentFactory. While it isn’t an API developers have to use, it can be regarded as a better design approach in certain situations and can help when testing Fragments with external dependencies.[1]
FragmentFactory
主要用于Fragment
的初始化,但是FragmentFactory
并非强制使用的.默认无参的Fragment
构造函数可以不使用FragmentFactory
,否则还是建议使用FragmentFactory
来进行实例化.[2]
源码分析
FragmentFactory最重要的方法 FragmentFactory#instantiate(ClassLoader, String)
,源码方法很短.
/**
* Create a new instance of a Fragment with the given class name. This uses
* {@link #loadFragmentClass(ClassLoader, String)} and the empty
* constructor of the resulting Class by default.
*
* @param classLoader The default classloader to use for instantiation
* @param className The class name of the fragment to instantiate.
* @return Returns a new fragment instance.
* @throws Fragment.InstantiationException If there is a failure in instantiating
* the given fragment class. This is a runtime exception; it is not
* normally expected to happen.
*/
@NonNull
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
try {
Class extends Fragment> cls = loadFragmentClass(classLoader, className);
return cls.getConstructor().newInstance();
} catch (java.lang.InstantiationException e) {
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (IllegalAccessException e) {
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (NoSuchMethodException e) {
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ ": could not find Fragment constructor", e);
} catch (InvocationTargetException e) {
throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ ": calling Fragment constructor caused an exception", e);
}
}
使用方法就是继承FragmentFactory
之后重写 FragmentFactory#instantiate(ClassLoader, String)
即可.[1]
//demo code![截屏2020-11-14 17.35.32.png](https://upload-images.jianshu.io/upload_images/1598412-b05f46325d44b35c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
class CustomFragmentFactory(private val dependency: Dependency) : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
if (className == CustomFragment::class.java.name) {
return CustomFragment(dependency)
}
return super.instantiate(classLoader, className)
}
}
为什么会有FragmentFactory?
没有FragmentFactory
之前,是简单直接粗暴调用Fragment#instantiate(@NonNull Context context, @NonNull String fname)
就算. 不过很多时候开发者写的Fragment
都会写一个static
的newInstance(params:object)
方法去实例化,当状态恢复的时候,直接调用Fragment#instantiate(@NonNull Context context, @NonNull String fname)
就会出错 -- InstantiationException
,所以才有了FragmentFactory
+FragmentManager#getFragmentFactory()
的搭配使用.
/**
* Like {@link #instantiate(Context, String, Bundle)} but with a null
* argument Bundle.
* @deprecated Use {@link FragmentManager#getFragmentFactory()} and
* {@link FragmentFactory#instantiate(ClassLoader, String)}
*/
@SuppressWarnings("deprecation")
@Deprecated
@NonNull
public static Fragment instantiate(@NonNull Context context, @NonNull String fname) {
return instantiate(context, fname, null);
}
用法
如下图所示,可以通过给FragmentManager
传入一个FragmentFactory
的对象(这里有个坑,下面会说到),之后对接下来的Fragment
做带参实例化处理,如果Fragment
可以无参实例化的话,可以不使用FragmentFactory
.
然后在需要的地方调起fm.getFragmentFactory().instantiate(context.getClassLoader(), name)
,
e.g. FragmentManager#restoreSaveState(state)
,FragmentContainerView(context,attrs,fm)
,FragmentTabHost#doTabChanged( tag, FragmentTransaction)
问题
上面提到,其实每个 FragmentManager
只有一个 FragmentFactory
的对象,而 activity
也是只有一个 FragmentManager
. 所以如果是那种[一个Activity多个业务Fragment]的模式,而且业务Fragment都在不同module以及不同framework的话,就不能够每个framework设置一个 FragmentFactory
了,否则就能够给其中一个framework使用了.如果打算每个framework设置一个 FragmentFactory
,之后Activity的 FragmentFactory
遍历各个framework的 FragmentFactory
也是不行的, 因为 FragmentFactory
的源码决定了, 如果找不到Fragment就会报错 , 当然也可以在 else
的时候,不调用 super.instantiate(classLoader, className)
,不过这样在团队开发的时候容易出错,造成APP crash.
为了解决这个问题,只能够把 FragmentFactory
放到Activity做扩展,在各个framework实现不同的delegate.
参考
- Android Fragments: FragmentFactory
- 带参构造函数为什么需要使用FragmentFactory
- [译][Google工程师] 详解 FragmentFactory 如何优雅使用 Koin 以及部分源码分析