今天,有同事在看了我的代码后,告诉我当我们的类继承自Fragment时,需要添加一个空的public构造方法。我很好奇问他为什么,他说官方建议我们这么做,不然可能会出问题,我们的产品已经被友盟统计到因Fragment没有空的构造方法而报错。
创建完后的代码如下。
我们发现,其中有一个空的构造方法,而且Google特意加了注释,Required empty public constructor。
以前在使用eclipse时,是不会出现这些的。
为了求证原因,我们去官方文档看一下。
文档链接:
https://developer.android.com/reference/android/support/v4/app/Fragment.html
然后找到如下部分。
每一个Fragment必须有一个空的构造方法,这样当Activity恢复状态时Fragment能够被实例化。强烈建议当我们继承Fragment类时,不要添加带有参数的构造方法,因为当Fragment被重新实例化时,这些构造方法不会被调用。如果需要给Fragment传递参数,可以调用setArguments(Bundle)方法,然后在Fragment中调用getArguments()来获取参数。
这段话告诉我们两点。第一,Fragment必须要有空的构造方法。第二,最好不要通过构造方法传递参数。
下面我们对上述两点进行分析。
(1).Fragment空的构造方法的作用
由官方文档可以看出,Fragment的空构造方法是在被恢复状态时使用到。这里我们很自然的联想到FragmentManager类,而FragmentManager是abstract的,它有一个唯一的实现类FragmentManagerImpl。在FragmentManagerImpl类中,我们找到恢复状态的方法restoreAllState()。
抽取出核心代码。
可以看到,这里调用了FragmentState类的instantiate()方法来创建Fragment对象。
我们找到FragmentState类的instantiate()方法。
会发现,其中又调用了Fragment类的instantiate()方法。这里就是我们想要的关键代码了,贴上完整的方法。
方法的注释:通过指定一个Fragment的类名,来创建一个Fragment对象。使用该方法,与使用Fragment的空的构造方法是一样的。
可以看到,在方法内部使用了Java的反射机制来创建对象。我们再找到下面一行关键代码。
在Java的反射中,使用Class对象的newInstance()方法创建对象,调用的是 该类的无参构造方法。如果该类没有无参构造方法,会抛出 java.lang.InstantiationException异常。
我们再看该方法catch的异常。
疑问已经解决。
(2).不使用构造方法,如何给Fragment传递参数
因为Fragment在恢复状态时,会使用空的构造方法来创建对象,所以我们不能在构造方法中传递参数了。
当我们需要用到Fragment对象时,使用它的静态方法newInstance(...)来创建。方法内部,使用了Bundle传递参数,并将Bundle传递到setArguments()方法中。然后,我们在Fragment的onCreate()方法中,就可以通过getArguments()来获取Bundle对象,进而获取到我们需要的参数。
晚上回来,打算研究下。
我们在Android Studio中创建Fragment类,如图所示。
创建完后的代码如下。
public class BlankFragment extends Fragment {
// 其它代码......
public BlankFragment() {
// Required empty public constructor
}
// 其它代码......
}
我们发现,其中有一个空的构造方法,而且Google特意加了注释,Required empty public constructor。
以前在使用eclipse时,是不会出现这些的。
为了求证原因,我们去官方文档看一下。
文档链接:
https://developer.android.com/reference/android/support/v4/app/Fragment.html
然后找到如下部分。
每一个Fragment必须有一个空的构造方法,这样当Activity恢复状态时Fragment能够被实例化。强烈建议当我们继承Fragment类时,不要添加带有参数的构造方法,因为当Fragment被重新实例化时,这些构造方法不会被调用。如果需要给Fragment传递参数,可以调用setArguments(Bundle)方法,然后在Fragment中调用getArguments()来获取参数。
这段话告诉我们两点。第一,Fragment必须要有空的构造方法。第二,最好不要通过构造方法传递参数。
下面我们对上述两点进行分析。
(1).Fragment空的构造方法的作用
由官方文档可以看出,Fragment的空构造方法是在被恢复状态时使用到。这里我们很自然的联想到FragmentManager类,而FragmentManager是abstract的,它有一个唯一的实现类FragmentManagerImpl。在FragmentManagerImpl类中,我们找到恢复状态的方法restoreAllState()。
抽取出核心代码。
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
// 其它代码......
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
// 其它代码......
Fragment f = fs.instantiate(mHost, mParent, childNonConfig);
// 其它代码......
}
}
// 其它代码......
}
可以看到,这里调用了FragmentState类的instantiate()方法来创建Fragment对象。
我们找到FragmentState类的instantiate()方法。
public Fragment instantiate(FragmentHostCallback host, Fragment parent,
FragmentManagerNonConfig childNonConfig) {
// 其它代码......
if (mInstance == null) {
mInstance = Fragment.instantiate(context, mClassName, mArguments);
}
// 其它代码......
return mInstance;
}
会发现,其中又调用了Fragment类的instantiate()方法。这里就是我们想要的关键代码了,贴上完整的方法。
/**
* Create a new instance of a Fragment with the given class name. This is
* the same as calling its empty constructor.
*/
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
try {
Class<?> clazz = sClassMap.get(fname);
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(fname);
sClassMap.put(fname, clazz);
}
Fragment f = (Fragment)clazz.newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.mArguments = args;
}
return f;
} catch (ClassNotFoundException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (java.lang.InstantiationException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (IllegalAccessException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
}
}
方法的注释:通过指定一个Fragment的类名,来创建一个Fragment对象。使用该方法,与使用Fragment的空的构造方法是一样的。
可以看到,在方法内部使用了Java的反射机制来创建对象。我们再找到下面一行关键代码。
Fragment f = (Fragment)clazz.newInstance();
在Java的反射中,使用Class对象的newInstance()方法创建对象,调用的是 该类的无参构造方法。如果该类没有无参构造方法,会抛出 java.lang.InstantiationException异常。
我们再看该方法catch的异常。
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
疑问已经解决。
(2).不使用构造方法,如何给Fragment传递参数
因为Fragment在恢复状态时,会使用空的构造方法来创建对象,所以我们不能在构造方法中传递参数了。
那么如何传递呢?使用Android Studio创建Fragment类时,Google已经为我们写好了传递参数的模板代码。
public class BlankFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
public BlankFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment BlankFragment.
*/
// TODO: Rename and change types and number of parameters
public static BlankFragment newInstance(String param1, String param2) {
BlankFragment fragment = new BlankFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
}
当我们需要用到Fragment对象时,使用它的静态方法newInstance(...)来创建。方法内部,使用了Bundle传递参数,并将Bundle传递到setArguments()方法中。然后,我们在Fragment的onCreate()方法中,就可以通过getArguments()来获取Bundle对象,进而获取到我们需要的参数。
本文探讨了在Android开发中Fragment为何需要一个空的构造方法,以及如何通过Android Studio提供的模板代码来传递参数。
1万+

被折叠的 条评论
为什么被折叠?



