您的位置:首页 > 移动开发 > Android开发

浅析Fragment为什么需要空的构造方法

2016-07-23 01:25 148 查看
今天,有同事在看了我的代码后,告诉我当我们的类继承自Fragment时,需要添加一个空的public构造方法。我很好奇问他为什么,他说官方建议我们这么做,不然可能会出问题,我们的产品已经被友盟统计到因Fragment没有空的构造方法而报错。
晚上回来,打算研究下。

我们在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对象,进而获取到我们需要的参数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息