1. 为何需要FragmentFactory
关于Fragment的使用约定
有Fragment使用经验的人都知道,Fragment必须有有一个空参的构造函数,否则打包时会提示一下错误:
This fragment should provide a default constructor (a public constructor with no arguments)
在添加了空参构造器之后,如果定义了任何带参数构造器,仍然会亲切的提示:
Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]
因为某些情况下(例如横竖屏旋转等)Fragment会由系统恢复重建,此时系统不知道该选择哪个构造函数,所以系统与开发者约定,统一使用默认的空参构造函数构建,然后通过setArgments
设置初始化值。
因此,我们常见的一个做法是通过静态方法,避免定义有参数的构造函数。如下,静态方法getInstance(String str)
中,先空参构造Fragment,然后通过setArgments
初始化。
public class MainFragment extends BaseFragment {
private static final String MY_ARG = "my_arg";
private String arg = "";
public static MainFragment getInstance(String str) {
MainFragment fragment = new MainFragment();
Bundle bundle = new Bundle();
bundle.putString(MY_ARG, str);
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
arg = getArguments().getString(MY_ARG);
}
}
}
后续便可以使用此静态方法构建Fragment了
MainFragment fragment = MainFragment.getInstance("Hello world!!");
Fragment恢复重建过程中,系统会调用静态方法Fragment.instantiate
(在onCreate
和onActivityCreated
之间)
@NonNull
public static Fragment instantiate(@NonNull Context context, @NonNull String fname,
@Nullable Bundle args) {
try {
Class extends Fragment> clazz = FragmentFactory.loadFragmentClass(
context.getClassLoader(), fname);
Fragment f = clazz.getConstructor().newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.setArguments(args);
}
return f;
} catch (java.lang.InstantiationException e) {
我们先前通过setArguments
传递的bundle
(随着onSaveInstanceState
保存),会被系统传递给instantiate
,以协助fragment的恢复重建。
AndroidX中新的处理方案
以上这些关于Fragment使用上的约定,随着AndroidX ver. 1.1.0-alpha01
的发布成为了历史。
AndroidX中,Fragment.instantiate
方法已经被标记位@Deprecated
,推荐使用FragmentManager.getFragmentFactory
和FragmentFactory.instantiate (ClassLoader, String)
作为替代。FragmentFactory作为新的解决方案,取消了以前fragment只能基于空参构造的约定,允许开发者按照需要自由定义其构造函数。
2. 使用方法
假设我们的MainFragment
需要两个参数,那么使用FragmentFactory如何构造呢?
![](/qrcode.jpg)
定义FragmentFactory
首先,需要定义自己的FragmentFactory。主要是重写instantiate方法,注意跟以前比,已经不支持传入Bundle args
作为参数了。即使你想使用bundle传参,也推荐在这里手动setArgument
,而非借助系统的设置。
class MyFragmentFactory extends FragmentFactory {
private final AnyArg anyArg1;
private final AnyArg anyArg2;
public MyFragmentFactory(AnyArg arg1, AnyArg arg2) {
this.anyArg1 = arg1;
this.anyArg2 = arg2;
}
@NonNull
@Override
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
Class extends Fragment> clazz = loadFragmentClass(classLoader, className);
if (clazz == MainFragment.class) {
return new MainFragment(anyArg1, anyArg2);
} else {
return super.instantiate(classLoader, className);
}
}
}
可见,使用FragmentFactory后,无需静态方法,可以使用构造函数传参即可:
protected MainFragment(AnyArg arg1, AnyArg arg2) {
this.arg1 = arg1;
this.arg2 = arg2;
}
设置Factory
接下来需要在合适的时机为FragmentManager设置此Factory
MyFragmentFactory fragmentFactory = new MyFragmentFactory( someObject1, someObject2);
@Override
public void onCreate(Bundle savedInstanceState) {
getSupportFragmentManager().setFragmentFactory(fragmentFactory);
super.onCreate(savedInstanceState);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
.replace(
R.id.fragment_container,
MainFragment.class);
if (addToBackStack) {
fragmentTransaction.addToBackStack(tag);
}
fragmentTransaction.commit();
}
fm添加fragment时,会使用factory创建实例。
需要特别注意的是,setFragmentFactory一定要在
super.onCreate
之前调用,因为在super.onCreate中会进行fragment的重建是需要被使用到。
总结
FragmentFactory的作用很简单:就是帮助开发者自定义并使用带参数的Fragment构造器。这对于dagger、koin等某些DI框架的使用场景中会非常有帮助