浅析Fragment为什么需要空的构造方法
2016-07-23 01:25
148 查看
今天,有同事在看了我的代码后,告诉我当我们的类继承自Fragment时,需要添加一个空的public构造方法。我很好奇问他为什么,他说官方建议我们这么做,不然可能会出问题,我们的产品已经被友盟统计到因Fragment没有空的构造方法而报错。
晚上回来,打算研究下。
我们在Android Studio中创建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在恢复状态时,会使用空的构造方法来创建对象,所以我们不能在构造方法中传递参数了。
那么如何传递呢?使用Android Studio创建Fragment类时,Google已经为我们写好了传递参数的模板代码。
当我们需要用到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对象,进而获取到我们需要的参数。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories