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

本文探讨了在Android开发中Fragment为何需要一个空的构造方法,以及如何通过Android Studio提供的模板代码来传递参数。
今天,有同事在看了我的代码后,告诉我当我们的类继承自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对象,进而获取到我们需要的参数。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值