$$如何向一个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之前使用。

posted @ 2018-03-05 10:32  chenxibobo  阅读(604)  评论(0编辑  收藏  举报