> 文档中心 > 【Android面试】为什么要用newInstance来实例化Fragment

【Android面试】为什么要用newInstance来实例化Fragment


Android日常研发中不可避免的肯定要用到Fragment,你如何使用的呢?Compare the two methods of use,是否觉得第二种更加简洁。这时很多人肯定提出疑问:这两种使用方式有何区别,我的代码中到底使用哪种方式更好一些,以及为什么要使用这种方式 and so on,各位看官稍安勿躁,且听老衲娓娓道来。

Usage 1:

@Override    public void initView(Bundle savedInstanceState) { BlankFragment mFragment = new BlankFragment(); Bundle bundle = new Bundle(); bundle.putString("arg1", "a"); bundle.putString("arg2", "b"); bundle.putString("arg3", "c"); mFragment.setArguments(bundle); getFragmentManager().beginTransaction().replace(R.id.frame, mFragment).commit();    }

Usage 2:

@Override    public void initView(Bundle savedInstanceState) { getFragmentManager().beginTransaction().replace(R.id.frame, BlankFragment.newInstance("a", "b")).commit();    }

首先我们新建一个fragment,我们一起来看一下android建议的fragment如何编写(请严格按照截图的来步步创建哦)
【Android面试】为什么要用newInstance来实例化Fragment

package com.itbird.utils;import android.content.Context;import android.net.Uri;import android.os.Bundle;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import com.itbird.base.R;/** * A simple {@link Fragment} subclass. * Activities that contain this fragment must implement the * {@link BlankFragment.OnFragmentInteractionListener} interface * to handle interaction events. * Use the {@link BlankFragment#newInstance} factory method to * create an instance of this fragment. */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;    private OnFragmentInteractionListener mListener;    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); }    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_blank, container, false);    }    // TODO: Rename method, update argument and hook method into UI event    public void onButtonPressed(Uri uri) { if (mListener != null) {     mListener.onFragmentInteraction(uri); }    }    @Override    public void onAttach(Context context) { super.onAttach(context); if (context instanceof OnFragmentInteractionListener) {     mListener = (OnFragmentInteractionListener) context; } else {     throw new RuntimeException(context.toString()      + " must implement OnFragmentInteractionListener"); }    }    @Override    public void onDetach() { super.onDetach(); mListener = null;    }    /**     * This interface must be implemented by activities that contain this     * fragment to allow an interaction in this fragment to be communicated     * to the activity and potentially other fragments contained in that     * activity.     * 

* See the Android Training lesson Communicating with Other Fragments for more information. */ public interface OnFragmentInteractionListener { // TODO: Update argument type and name void onFragmentInteraction(Uri uri); }}

上述代码其实就是在一个Fragment的newInstance方法中传递两个参数,并且通过fragment.setArgument保存在它自己身上,而后通过onCreate()调用的时候将这些参数取出来。这样写没什么特殊的啊,不就是用静态工厂方法传个参数么,用构造器传参数不一样处理么?No,No,No,如果仅仅是个静态工厂而已,又怎么能成为谷歌推荐呢。

实践是检验真理的唯一标准,我们一起通过一个样例来实际操作一番
fragment_main.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <framelayout android:id="@+id/layout_top" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/>    <framelayout android:id="@+id/layout_bottom" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/></LinearLayout>

【Android面试】为什么要用newInstance来实例化Fragment
由图和代码可知,我们在xml中定义两个FrameLayout,平分整个屏幕高度

MainActivity.java

package com.itbird.myapplication;import android.os.Bundle;import android.support.v4.app.FragmentTransaction;public class MainActivity extends BaseActivity {    @Override    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_main); if (savedInstanceState == null) {     FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();     transaction.add(R.id.layout_top, new BlankFragment("顶部的Fragment", "test"));     transaction.add(R.id.layout_bottom, BlankFragment.newInstance("底部的Fragment", "test"));     transaction.commit(); }    }}

BlankFragment.java

package com.itbird.myapplication;import android.annotation.SuppressLint;import android.content.Context;import android.net.Uri;import android.os.Bundle;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.TextView;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    }    @SuppressLint("ValidFragment")    public BlankFragment(String mParam1, String mParam2) { this.mParam1 = mParam1; this.mParam2 = mParam2;    }    /**     * 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); }    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_blank, container, false); TextView textView = view.findViewById(R.id.text); textView.setText(mParam1 + mParam2); return view;    }}

fragment_blank.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <TextView android:id="@+id/text" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" /></FrameLayout>

通过阅读代码可知,我们通过两种不同的方式创建fragment,同样在其中心textview中展示相应拼接字段。

【Android面试】为什么要用newInstance来实例化Fragment

嗯,效果如预期的一样完美,此时,我们把屏幕横过来,看看会出现怎样的状况

【Android面试】为什么要用newInstance来实例化Fragment

My god,顶部的fragment 文本内容咋都变成null了。。。
我们来分析一下产生上述情况的原因:当我们横竖屏切换的时候,activity会重建,相应的,依附于它上面的Fragment也会重新创建。好,顺着这个思路,进activity的onCreate方法中看看:

protected void onCreate(@Nullable Bundle savedInstanceState) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState); if (mLastNonConfigurationInstances != null) {     mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders); } if (mActivityInfo.parentActivityName != null) {     if (mActionBar == null) {  mEnableDefaultActionBarUp = true;     } else {  mActionBar.setDefaultDisplayHomeAsUpEnabled(true);     } } if (savedInstanceState != null) {     Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);     mFragments.restoreAllState(p, mLastNonConfigurationInstances != null      ? mLastNonConfigurationInstances.fragments : null); } mFragments.dispatchCreate(); getApplication().dispatchActivityCreated(this, savedInstanceState); if (mVoiceInteractor != null) {     mVoiceInteractor.attachActivity(this); } mCalled = true;    }

显而易见,fragment的重建是在restoreAllState方法中,跟进
FragmentController.java

 /**     * Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment     * instances retained across configuration changes, including nested fragments     *     * @see #retainNestedNonConfig()     */    public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) { mHost.mFragmentManager.restoreAllState(state, nonConfig);    }

继续跟进
FragmentManager.java

 void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) { // If there is no saved state at all, then there can not be // any nonConfig fragments either, so that is that. if (state == null) return; FragmentManagerState fms = (FragmentManagerState)state; if (fms.mActive == null) return; List<FragmentManagerNonConfig> childNonConfigs = null; // First re-attach any non-config instances we are retaining back // to their saved state, so we don't try to instantiate them again. ...  // Build the full list of active fragments, instantiating them from // their saved state. mActive = new ArrayList<>(fms.mActive.length); if (mAvailIndices != null) {     mAvailIndices.clear(); } 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);  }  Fragment f = fs.instantiate(mHost, mParent, childNonConfig);  if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);  mActive.add(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;     } else {  mActive.add(null);  if (mAvailIndices == null) {      mAvailIndices = new ArrayList<>();  }  if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);  mAvailIndices.add(i);     } }  // Update the target of all retained fragments.... // Build the list of currently added fragments....  // Build the back stack. ...    }

通过阅读, 找到关键代码

 Fragment f = fs.instantiate(mHost, mParent, childNonConfig);

然后锲而不舍跟进
FragmentManager.java

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

跟进到这里,终于有点头绪了,至少看到fragment实例化的地方了,迫不及待的再次点击去view一下下
Fragment.java

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 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对象被反射创建之后,会调用这么一句代码
f.mArguments = args;
哦,なるほど(原来如此),Fragment在重新创建的时候只会调用无参的构造方法,并且如果之前通过fragment.setArguments(bundle)这种方式设置过参数的话,Fragment重建时会得到这些参数,所以,在onCreate中我们可以通过getArguments()的方式拿到我们之前设置的参数。同时由于Fragment在重建时并不会调用我们自定义的带参数的构造方法,所以我们传递的参数它也就获取不到了。

也许有网友依然会继续追问,重新set时,mArguments确定不会为空吗?Fragment销毁时,这个变量不会置空吗?我们通过源码看一下:
Fragment.java

 /**     * Called when the view previously created by {@link #onCreateView} has     * been detached from the fragment.  The next time the fragment needs     * to be displayed, a new view will be created.  This is called     * after {@link #onStop()} and before {@link #onDestroy()}.  It is called     * regardless of whether {@link #onCreateView} returned a     * non-null view.  Internally it is called after the view's state has     * been saved but before it has been removed from its parent.     */    @CallSuper    public void onDestroyView() { mCalled = true;    }    /**     * Called when the fragment is no longer in use.  This is called     * after {@link #onStop()} and before {@link #onDetach()}.     */    @CallSuper    public void onDestroy() { mCalled = true; //Log.v("foo", "onDestroy: mCheckedForLoaderManager=" + mCheckedForLoaderManager // + " mLoaderManager=" + mLoaderManager); if (!mCheckedForLoaderManager) {     mCheckedForLoaderManager = true;     mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) {     mLoaderManager.doDestroy(); }    }

看到此处,相信各位看官已经有“了然大明白”的感觉了,我就不再多说了。

##总结
1.通过对比两种使用方式,我们知道两种方式别无其他,只是事关风格而已(代码”整”“洁”之道)
2.使用Fragment过程中在涉及到传参时,千万不要通过构造方法或者setParam方式直接赋值传入参数,必须使用setArguments来传参,否则程序在某些应用情景下,会丢参
强烈建议:两者虽无严格的对错之分,都可以使用,但是newInstance方式无论从代码整洁之道还是程序规范的稳定性而言,都是每个程序员应该学习使用的方式。