$$如何向一个Fragment传递参数---setArguments方法的介绍
在我们平常开发中经常会用到Fragment,当我们使用Fragment时一般是通过new Fragment的构造方法来实现,如果我问你怎么向一个Fragment传递参数,你是不是会首先想到通过构造方法,当面试被问到这个问题的时候我也是这么想的,后来发现自己错了,现在给大家讲一下究竟该怎么做。
首先我们看构造方法这种方式为什么不行,根据Android文档说明,当一个fragment重新创建的时候,系统会再次调用 Fragment中的默认构造函数。 注意这里:是 默认构造函数。
这句话更直白的意思就是:当你小心翼翼的创建了一个带有重要参数的Fragment的之后,一旦由于什么原因(横竖屏切换)导致你的Fragment重新创建。——-很遗憾的告诉你,你之前传递的参数都不见了,因为recreate你的Fragment的时候,调用的是默认构造函数。
首先我们通过构造函数来传递参数,代码如下
public class MainActivity extends FragmentActivity {
private FragmentManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
manager = getSupportFragmentManager();
/*
* 这里为什么要进行空判断,因为在屏幕旋转的时候,系统会执行onSaveInstanceState
* 方法去保存当前activity的状态,然后activity会重建,执行onCreate方法,如果我们不判断
* savedInstanceState是否为空,那么每次就会执行下面的commit操作,向Fragmnet传递参数,
* 这样参数的却会保留下来,但是我们不应该每次都去传递参数。当进行了空判断时,当Activity重建
* 的时候,会调用Fragment的默认构造函数,所以我们传递过去的参数不能保留了。
*/
if(savedInstanceState == null){
manager.beginTransaction().replace(R.id.fl_main, new FragmentOne("params")).commit();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
System.out.println("=========savedInstanceState ");
super.onSaveInstanceState(outState);
}
}
public class FragmentOne extends Fragment {
private TextView textView;
private String params = "default";
public FragmentOne(){
System.out.println("===========default constructor");
}
/**
* 通过构造方法接收传递过来的参数
* @param content
*/
public FragmentOne(String content){
params = content;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment1, container,false);
textView = (TextView) view.findViewById(R.id.textview);
textView.setText(params);
return view;
}
}
可以看到此时传递过来的参数已经不见了,说明通过构造方法传递参数是不行的。
我们看看控制台的打印
注意:
这里我们必须写出默认的构造函数,因为Fragment重建的时候,会调用默认的构造函数,也就是空参数的构造函数,如果我们只是给出了带参数的构造函数,系统是不会为我们创建空参数的构造函数的,如果你不写,在Fragment重建的时候就会发生下面的错误。
接下来看看官方推荐的setArguments方法:
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fl_main, FragmentOne.newInstance("params"))
.commit();
}
}
}
public class FragmentOne extends Fragment{
private TextView textView;
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_one, null);
textView = (TextView) view.findViewById(R.id.textview);
if(getArguments()!=null){
//取出保存的值
textView.setText(getArguments().getString("name"));
}
return view;
}
public static FragmentOne newInstance(String text){
FragmentOne fragmentOne = new FragmentOne();
Bundle bundle = new Bundle();
bundle.putString("name", text);
//fragment保存参数,传入一个Bundle对象
fragmentOne.setArguments(bundle);
return fragmentOne;
}
}
可以看到,屏幕旋转以后参数也保留下来了。
接下来我们通过源码看看Bundle这个参数到底如何保留下来的,
点进去Fragment的setArguments方法:
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
首先将当前的bundle对象赋值给一个全局的mArguments对象,mArguments时FragmentState对象的一个属性,FragmentState时Fragment的一个内部类,代表着Fragment的状态。
final class FragmentState implements Parcelable {
final String mClassName;
final int mIndex;
final boolean mFromLayout;
final int mFragmentId;
final int mContainerId;
final String mTag;
final boolean mRetainInstance;
final boolean mDetached;
final Bundle mArguments;
final boolean mHidden;
Bundle mSavedFragmentState;
Fragment mInstance;
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
mFromLayout = frag.mFromLayout;
mFragmentId = frag.mFragmentId;
mContainerId = frag.mContainerId;
mTag = frag.mTag;
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
mHidden = frag.mHidden;
}
然后Activity保存状态的时候会调用onSaveInstanceState方法
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
// 调用saveAllState方法保存Fragment状态
Parcelable p = mFragments.saveAllState();
if (p != null) {
// 将结果保存到Bundle中
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
saveAllState方法最终调用到FragmentManager的saveAllState方法中
Parcelable saveAllState() {
// 找到所有的存活的Fragment
int N = mActive.size();
// 代表Fragment状态的数组
FragmentState[] active = new FragmentState[N];
boolean haveFragments = false;
for (int i=0; i<N; i++) {
Fragment f = mActive.valueAt(i);
if (f != null) {
if (f.mIndex < 0) {
throwException(new IllegalStateException(
"Failure saving state: active " + f
+ " has cleared index: " + f.mIndex));
}
haveFragments = true;
// 找到所有的Fragment,为FragmentState数组初始化
FragmentState fs = new FragmentState(f);
active[i] = fs;
// 保证Fragment已经创建了并且没有参数保存过
if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
// 保存Fragment的状态
fs.mSavedFragmentState = saveFragmentBasicState(f);
FragmentManagerState fms = new FragmentManagerState();
// active是上面代表Fragment状态的数组,至此,fragment的状态就被保存到
了FragmentManagerState中
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
fms.mNextFragmentIndex = mNextFragmentIndex;
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
}
saveNonConfig();
return fms;
}
接下来我们看看恢复数据的流程
在Activity的onCreate中有下面的代码
// 取出之前保存的数据
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
最终调用到了FragmentManager的restoreAllState方法
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
if (state == null) return;
// 取出保存的数据
FragmentManagerState fms = (FragmentManagerState)state;
if (fms.mActive == null) return;
// ... 省去部分代码
// Build the full list of active fragments, instantiating them from
// their saved state.
// 根据之前保存的状态初始化新的Fragment
mActive = new SparseArray<>(fms.mActive.length);
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
FragmentManagerNonConfig childNonConfig = null;
if (childNonConfigs != null && i < childNonConfigs.size()) {
childNonConfig = childNonConfigs.get(i);
}
// 调用instantiate方法创建Fragment
Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.put(f.mIndex, f);
// Now that the fragment is instantiated (or came from being
// retained above), clear mInstance in case we end up re-restoring
// from this FragmentState again.
fs.mInstance = null;
}
}
public Fragment instantiate(FragmentHostCallback host, FragmentContainer container,
Fragment parent, FragmentManagerNonConfig childNonConfig) {
if (mInstance == null) {
final Context context = host.getContext();
if (mArguments != null) {
mArguments.setClassLoader(context.getClassLoader());
}
// 初始化Fragment,把之前保存的参数传过去
if (container != null) {
mInstance = container.instantiate(context, mClassName, mArguments);
} else {
mInstance = Fragment.instantiate(context, mClassName, mArguments);
}
if (mSavedFragmentState != null) {
mSavedFragmentState.setClassLoader(context.getClassLoader());
mInstance.mSavedFragmentState = mSavedFragmentState;
}
mInstance.setIndex(mIndex, parent);
mInstance.mFromLayout = mFromLayout;
mInstance.mRestored = true;
mInstance.mFragmentId = mFragmentId;
mInstance.mContainerId = mContainerId;
mInstance.mTag = mTag;
mInstance.mRetainInstance = mRetainInstance;
mInstance.mDetached = mDetached;
mInstance.mHidden = mHidden;
mInstance.mFragmentManager = host.mFragmentManager;
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
"Instantiated fragment " + mInstance);
}
mInstance.mChildNonConfig = childNonConfig;
return mInstance;
}
// 初始化的方法
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);
if (!Fragment.class.isAssignableFrom(clazz)) {
throw new InstantiationException("Trying to instantiate a class " + fname
+ " that is not a Fragment", new ClassCastException());
}
sClassMap.put(fname, clazz);
}
// 调用Fragment无参数的构造函数
Fragment f = (Fragment) clazz.getConstructor().newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
// 设置参数,然后我们就可以用getArgument方法获取了
f.setArguments(args);
}
return f;
注意:
setArguments方法的调用必须要在Fragment与Activity关联之前。
这句话可以这样理解,setArgument方法的使用必须要在FragmentTransaction 的commit之前使用。