Android(安卓)从源码的角度分析——为什么要用newInstance来实例化Fragment
最近在看Google技术文档的时候发现了一种新的方式来实例化Fragment,就是采用静态工厂的方式创建Fragment。我们在使用Android studio创建一个类的时候,选择New ->Fragment->Fragment(Blank)可以很直观的看到这种方式的写法:
public class BlankFragment extends Fragment { private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; 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方法中传递两个参数,并且通过fragment.setArgument保存在它自己身上,而后通过onCreate()调用的时候将这些参数取出来。
可能有人乍一看,这样写没什么特殊的啊,不就是用静态工厂方法传个参数么,用构造器传参数不一样处理么?No,No,No,如果仅仅是个静态工厂而已,又怎么能成为谷歌推荐呢。
我们先来看一个小例子:
<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>
在xml中定义两个FrameLayout,平分整个屏幕高度。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(savedInstanceState == null){ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.add(R.id.layout_top,new TopFragment("顶部的Fragment")); transaction.add(R.id.layout_bottom,BottomFragment.newInstance("底部的Fragment")); transaction.commit(); } }
在activity中采用两种不同的方式来实例化Fragment,顶部的Fragment通过构造方法将参数传递给它,而底部的Fragment通过newInstance的方式实例化并传参。两个Fragment的代码如下所示:
public class TopFragment extends Fragment { private String mTop = "啥也没有"; public TopFragment(){ } public TopFragment(String top) { this.mTop = top; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { TextView tv = new TextView(getActivity()); tv.setText(mTop); tv.setGravity(Gravity.CENTER); tv.setTextColor(Color.RED); tv.setTextSize(25); return tv; }}
public class BottomFragment extends Fragment { private String mBottom = "啥也没有"; public static BottomFragment newInstance(String bottom) { BottomFragment fragment = new BottomFragment(); Bundle bundle = new Bundle(); bundle.putString("bottom",bottom); fragment.setArguments(bundle); return fragment; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(getArguments() != null){ mBottom = getArguments().getString("bottom"); } } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { TextView tv = new TextView(getActivity()); tv.setText(mBottom); tv.setGravity(Gravity.CENTER); tv.setTextColor(Color.RED); tv.setTextSize(25); return tv; }}
在两个Fragment第一行都写一个相同的默认参数:“啥也没有”,ok,运行工程:
嗯,没毛病,两个Fragment都顺利的接收到来自activity的数据。然后我们把屏幕横过来,看看会出现怎样的状况:
咦。。。顶部的Fragment的数据呢?为什么只显示默认的数据?activity给它传过去的数据哪去了呢?
我们来分析一下产生上述情况的原因:当我们横竖屏切换的时候,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);//这里,会恢复所有Fragment的状态 } mFragments.dispatchCreate(); getApplication().dispatchActivityCreated(this, savedInstanceState); if (mVoiceInteractor != null) { mVoiceInteractor.attachActivity(this); } mCalled = true; }
显而易见,所有Fragment的状态恢复应该是在mFragments.restoreAllState()这个方法,跟进去看看:
public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) { mHost.mFragmentManager.restoreAllState(state, nonConfigList);}
找到FragmentManager这个类,查看它的restoreAllState方法:
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) { ... 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<Integer>(); } if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i); mAvailIndices.add(i); } } ...}
寻找关键代码,我们发现了Fragment f = fs.instantiate(mHost, mParent, childNonConfig);这句,这里应该是Fragment重新实例化的地方了吧,赶紧点进去瞧瞧:
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);//创建Fragment对象的地方 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; }
继续跟进mInstance = Fragment.instantiate(context, mClassName, mArguments);看看里面的真正实现:
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对象被反射创建之后,会调用这么一句代码:f.mArguments = args; 哦,原来如此,Fragment在重新创建的时候只会调用无参的构造方法,并且如果之前通过fragment.setArguments(bundle)这种方式设置过参数的话,Fragment重建时会得到这些参数,所以,在onCreate中我们可以通过getArguments()的方式拿到我们之前设置的参数。同时由于Fragment在重建时并不会调用我们自定义的带参数的构造方法,所以我们传递的参数它也就获取不到了,这就是为什么会出现上述情况的原因。
细心的童鞋可以发现,上面的代码在catch语句当中抛出了几个异常,意思是:在Fragment重建过程中,确保你的Fragment的类是public的,并且带有一个public的空参的构造器,否则就让你崩溃~~~
好了,抛弃之前那些不好的代码习惯吧,支持谷歌,拥抱变化。
更多相关文章
- Android进阶(十六)子线程调用Toast报Can't create handler insid
- Android(安卓)三种动画详解及简单实例
- Android(安卓)三种播放视频的方式
- Android(安卓)通过findViewById方式创建TabHost
- Android(安卓)SurfaceFlinger 学习之路(七)----创建图形缓冲区Gr
- [转]android adapter 深刻分析
- Android之Handler源码分析(第五篇:移除消息)
- Android(安卓)获得Url、Uri字符串后面拼接的参数
- Android(安卓)OpenGL ES 分析与实践(2)