大家都知道Fragment的数据传递是用setArgument来完成的,但是,实际使用的时候需要注意一些问题,也就是调用setArgument的时机。
比如下面的情况:
首先,在Activity的xml布局文件中引用fragment:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/answer_question_fragment_root"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="@+id/answer_question_fragmentyyy"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.emiage.bank.assistant.answer.AnswerQuestionFragment"
tools:layout="@layout/fragment_answer_question" />
</RelativeLayout>
然后,我在Activity中试图通过setArgument来传递一些必要的参数:
@Override
protected void initViews() {
FragmentManager fragmentManager = getSupportFragmentManager();
AnswerQuestionFragment fragment = (AnswerQuestionFragment) fragmentManager.findFragmentById(R.id.answer_question_fragment);
String categoryName = getIntent().getStringExtra(ExtraName.CATEGORYNAME);
Bundle bundle = new Bundle();
bundle.putString(ExtraName.CATEGORYNAME, categoryName);
fragment.setArguments(bundle);
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.answer_question_fragment_root, fragment);
transaction.commitAllowingStateLoss();
}
这时候就会抛出上面的异常了:”Fragment already active”。异常是在setArgument的时候抛出的,我们看看setArgument方法:
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* creation.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
方法注释上说的很清楚了,setArgument方法要在“attach to activity”之前、构造fragment后立即调用,或许你还在疑惑,我就是在构造后立即调用的啊,怎么回事?其实真正的构造发生在解析xml文件时,通过findFragmentById得到的fragment已经是初始化过了,再调用setArgument就触发了上面的异常。
那么,如何纠正错误?很简单,把xml中的fragment标签去掉,只留下用来放fragment的容器。然后在Activity中commit:
@Override
protected void initViews() {
FragmentManager fragmentManager = getSupportFragmentManager();
AnswerQuestionFragment fragment = new AnswerQuestionFragment();
String categoryName = getIntent().getStringExtra(ExtraName.CATEGORYNAME);
Bundle bundle = new Bundle();
bundle.putString(ExtraName.CATEGORYNAME, categoryName);
fragment.setArguments(bundle);
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.answer_question_fragment_root, fragment);
transaction.commitAllowingStateLoss();
}
所以,如果要给fragment传递参数,最好不要使用xml布局引用的方式,而是通过构造new出来,然后设置要传递的参数,再add-commit。
注意
我注意到setArgument方法下面还有一个方法也会同样的方式抛出上面的异常:
/**
* Set the initial saved state that this Fragment should restore itself
* from when first being constructed, as returned by
* {@link FragmentManager#saveFragmentInstanceState(Fragment)
* FragmentManager.saveFragmentInstanceState}.
*
* @param state The state the fragment should be restored from.
*/
public void setInitialSavedState(SavedState state) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mSavedFragmentState = state != null && state.mState != null
? state.mState : null;
}
用来保存fragment状态的方法,用来恢复最后保存状态,从代码上来看,这里触发异常的条件是跟setArgument方法一样的,所以,使用上也要注意同样情况。