正确使用Fragment之创建/传参——newInstance方法(native)

说来忏愧,近来越发觉得写不出可分享的东西,更糟糕的是,甚至觉得可记录的东西都不多。
这实在是一个非常糟的信号——说明我开始逐渐把自己放在安全边际内了。
人若总是将自己畏缩在安全边际之内,不去做一些阵痛的改变,埋下的会是病来如山倒般的灾难种子。
好在,好在我还在不断的学习,只是但前处于一种较混沌的状态,需要踏出去更多一步。

今天来说一个简单的话题,找回一些状态。

关于Fragment,相信大家已经熟之不能再熟了。然而,

使用频率如此之高的Fragment,你的使用姿势,真的正确吗?

先对比一下两种使用姿势:
1.姿势A:
MyFragment mFragment = new MyFragment();
         Bundle bundle = new Bundle();
         bundle.putString("arg1", "a");
         bundle.putString("arg2", "b");
         bundle.putString("arg3", "c");
         mFragment.setArguments(bundle);
         getSupportFragmentManager().beginTransaction().replace(R.id.frame, mFragment).commit();
2.姿势B:
 MyFragment mFragment = MyFragment.newInstance("a", "b","c");
 getSupportFragmentManager().beginTransaction().replace(R.id.frame, mFragment).commit();

有没有,有没有觉得第二种姿势特别爽
接来下进入今天的正题,关于Fragment.newInstance()这个方法。

我先声明,其实第一种姿势没什么问题,(引用斯坦福白胡子老头一句话)”这只是代码风格的问题,但我不建议这么做。”

使用Android Studio新建一个Fragment就一切明了了:
我们看到,Studio默认帮我们创建的Fragment中,有这样一段代码:

// 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;
}

一个静态方法,返回我们创建的Fragment类本身,显而易见的是,这个方法帮我们做了姿势A中我们手写的方法。

再来关注看我们较少Override的方法onCreate(这里默认直接帮我们Override了)

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);
    }
}

到这里,我们先捋一捋。姿势B的机理在于,通过传递参数给Fragment.newInstance()方法,它会创建一个该Fragment类,并通过创建Bundle把我们的参数代入。然后在onCreate()生命周期中,把参数拿回出来。(为什么这么做?是本文后半部分传参讨论的内容,先跳过),之后的事情大家都是熟手了,把参数拿来用就好。


为什么谷歌默认要使用这样一个工厂方式创建我们的Fragment呢?

既然newInstance()是父类Fragment的方法,我们跟进去一看究竟:

 //可以看到这是一个native方法
 public native T newInstance() throws InstantiationException, IllegalAccessException;

题外话:关于native方法: native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

就感觉线索断了一样,这是要往下去读C/C++啊,抛开底层机理不知,不说,姑且猜测为,二者完全是一样的方式,只是姿势B封装了一点内容,让Fragment的宿主Activity更加整洁一些,仅此而已。


既然如此,我们转为本文的下半部分,关于传参。

想过吗?Fragment作为java类
为什么传参需要用Fragment.setArguments(bundle)这样的方式,
而不通过构造函数直接传递new Fragment(arg1,arg2);

实践出真知,其实在大多数时候,这两种方法传递参数都是没有问题的。
但是,但是当某些情景发生,一切就不一样了。(比如竖屏切换横屏时),切换到横屏时,构造方法传递的参数就找不到了。

原因很简单,因为Fragment是有自己封装的生命周期的,这一点和Activity类似,Activity传参也不是用构造方法的方式。
但是究竟生命周期对构造方法传递参数有什么影响呢?

源码中一探究竟:

在Fragment中,是通过Bundle来保存参数的,它的私有声明在此:

Bundle mArguments;

顺着这个声明的命名mArguments找下去,发现其实相关的主要方法并不多:

 public FragmentState(Fragment frag) {
    ...
    mArguments = frag.mArguments;
    ...
}
  public void setArguments(Bundle args) {
        if (mIndex >= 0) {
            throw new IllegalStateException("Fragment already active");
        }
        mArguments = args;
    }
   final public Bundle getArguments() {
        return mArguments;
    }

这三个比较简单,就不说了

public Fragment instantiate(FragmentHostCallback host, Fragment parent,
            FragmentManagerNonConfig childNonConfig) {
        if (mInstance == null) {
            final Context context = host.getContext();
            if (mArguments != null) {
                mArguments.setClassLoader(context.getClassLoader());
            }
            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;
    }

在instantiate()实例化过程中,可以看到
if (mArguments != null) {
mArguments.setClassLoader(context.getClassLoader());
}
也就是说,如果我们调用时使用setArguments()传递了Bundle,它会被保存在mArguments 这个私有声明中。
而如果是通过构造函数传递的参数,那很不幸,Fragment重建过程中,并没有持有相应参数的属性或方法,自然,你通过构造函数传递的参数就丢失了。

其实目前大家单纯无参new Fragment()的方式并没有错,只是可以让Activity更优雅的调用Fragment.newInstance(),
而如果涉及到传递参数,万不可通过构造函数传递,会丢失。

知其然,知其所以然

总结,Fragment.newInstance() ,别无其他,只是事关风格(代码”整”“洁”之道),建议大家以后均使用谷歌推荐的该方法

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值