最近在看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的空参的构造器,否则就让你崩溃~~~

好了,抛弃之前那些不好的代码习惯吧,支持谷歌,拥抱变化。

更多相关文章

  1. Android进阶(十六)子线程调用Toast报Can't create handler insid
  2. Android(安卓)三种动画详解及简单实例
  3. Android(安卓)三种播放视频的方式
  4. Android(安卓)通过findViewById方式创建TabHost
  5. Android(安卓)SurfaceFlinger 学习之路(七)----创建图形缓冲区Gr
  6. [转]android adapter 深刻分析
  7. Android之Handler源码分析(第五篇:移除消息)
  8. Android(安卓)获得Url、Uri字符串后面拼接的参数
  9. Android(安卓)OpenGL ES 分析与实践(2)

随机推荐

  1. 传智播客—Android(三)数据存储之XML解析技
  2. android中访问和解析xml文件
  3. [置顶] 蓝牙防丢器原理、实现与Android(
  4. Android(安卓)项目部署之Nexus私服搭建和
  5. 四极管: 微软的移动战略:抽血Android实现三
  6. 上海靠谱社交公司职位:大前端负责人(对标阿
  7. 【精品推荐】200多种Android动画效果的强
  8. Android进程保活的一般套路
  9. Android(安卓)Studio导入Project、Module
  10. Android(安卓)仿微信之界面导航篇(一)