转载自:http://blog.csdn.net/johnnyz1234/article/details/45919907
在实际项目开发使用Fragment的时候,也碰到一些异常和存在的问题,下面做下简单的总结笔记,后面还会不定时补充更新。
1.关于Fragment的生命周期的几点认识
- Fragment的完整生命周期开始于绑定到它的父Activity,结束于从父Activity上分离。通过分别调用onAttach和onDetach来表示这些事件。
- 在Fragment/Activity 被暂停之后,由于任何其他处理程序都可以被调用,可能就会出现它的父Activity进程没有完成它的全部生命周期被终止从而导致onDetach不会被调用的情况。
- onAttach事件在Fragment的UI被创建之前,以及Fragment自身或它的父Activity完成它们的初始化之前被触发。通常情况下,onAttach事件用来获取一个Fragment的父Activity的引用,为进一步的初始化工作准备。
- 对Activity的进程来说,在没有响应的onDestroy方法被调用而被终止的情况很常见,所以Fragment不能依赖触发onDestory方法来销毁它。
- 如果Fragment需要和它的父Activity的UI交互,需要一直等到onActivityCreated事件被触发。该事件被触发意味着Frament所在的Activity已经完成了对初始化并且它的UI也已经完全构建好了。
2.Fragment开发中遇到的问题
- Fragment getActivity为空的情况解决办法
我们模仿QQ首页的实现Demo来模拟解决getActivity为空的问题,实现界面如下:
- public class SwitchActivity extends FragmentActivity {
- private Button btn_message,btn_call;
- private CallFragment callFragment;
- private MessageFragment messageFragment;
- public static final int MESSAGE_FRAGMENT_TYPE = 1;
- public static final int CALL_FRAGMENT_TYPE = 2;
- public int currentFragmentType = -1;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- this.requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.activity_switch);
- btn_message = (Button)findViewById(R.id.btn_message);
- btn_call = (Button)findViewById(R.id.btn_call);
- btn_message.setOnClickListener(onClicker);
- btn_call.setOnClickListener(onClicker);
- FragmentManager fragmentManager = getSupportFragmentManager();
- if (savedInstanceState != null) {
- //当savedInstanceState不为空的时候,说明当前的Activity是被回收后重建,
- //我们重新建立Fragment和Activity的联系。
- int type = savedInstanceState.getInt("currentFragmentType");
- messageFragment = (MessageFragment)fragmentManager.findFragmentByTag("message");
- callFragment = (CallFragment)fragmentManager.findFragmentByTag("call");
- if(type > 0)
- loadFragment(type);
- } else {
- FragmentTransaction transaction = fragmentManager
- .beginTransaction();
- Fragment mainFragment = fragmentManager.findFragmentByTag("message");
- if (mainFragment != null) {
- transaction.replace(R.id.fl_content, mainFragment);
- transaction.commit();
- } else {
- loadFragment(MESSAGE_FRAGMENT_TYPE);
- }
- }
- }
- /**
- * 当某种原因Activity被销毁回收掉(如:App进入后台运行或者Activity压入任务栈内存不足时候被回收),
- * onSaveIntanceState方法保存当前的状态。当用户操作当前Activity要返回前台或者
- * 我们的Activity会被重建(如:横屏操作),此时Activity与Fragment间失去联系,
- * 我们这个时候调用getActivity()会返回为null
- */
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt("lastFragmentTag", currentFragmentType);
- }
- private void switchFragment(int type) {
- switch (type) {
- case MESSAGE_FRAGMENT_TYPE:
- loadFragment(MESSAGE_FRAGMENT_TYPE);
- break;
- case CALL_FRAGMENT_TYPE:
- loadFragment(CALL_FRAGMENT_TYPE);
- break;
- }
- }
- private void loadFragment(int type) {
- FragmentManager fragmentManager = getSupportFragmentManager();
- FragmentTransaction transaction = fragmentManager.beginTransaction();
- if (type == CALL_FRAGMENT_TYPE) {
- if (callFragment == null) {
- callFragment = new CallFragment();
- transaction.add(R.id.fl_content, callFragment, "zhishi");
- } else {
- transaction.show(callFragment);
- }
- if (messageFragment != null) {
- transaction.hide(messageFragment);
- }
- currentFragmentType = MESSAGE_FRAGMENT_TYPE;
- } else {
- if (messageFragment == null) {
- messageFragment = new MessageFragment();
- transaction.add(R.id.fl_content, messageFragment, "wenda");
- } else {
- transaction.show(messageFragment);
- }
- if (callFragment != null) {
- transaction.hide(callFragment);
- }
- currentFragmentType = CALL_FRAGMENT_TYPE;
- }
- transaction.commitAllowingStateLoss();
- }
- private OnClickListener onClicker = new OnClickListener() {
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.btn_message:
- btn_message.setTextColor(Color.parseColor("#df3031"));
- btn_call.setTextColor(Color.WHITE);
- btn_message.setBackgroundResource(R.drawable.baike_btn_pink_left_f_96);
- btn_call.setBackgroundResource(R.drawable.baike_btn_trans_right_f_96);
- switchFragment(MESSAGE_FRAGMENT_TYPE);
- break;
- case R.id.btn_call:
- btn_message.setTextColor(Color.WHITE);
- btn_call.setTextColor(Color.parseColor("#df3031"));
- btn_message.setBackgroundResource(R.drawable.baike_btn_trans_left_f_96);
- btn_call.setBackgroundResource(R.drawable.baike_btn_pink_right_f_96);
- switchFragment(CALL_FRAGMENT_TYPE);
- break;
- }
- }
- };
- }
从以上代码我们知道,Activity页面实现对MessageFragment和CallFragment的切换功能。我们的页面可能在某种原因下系统被销毁回收掉(如:App进入后台运行或者Activity压入任务栈内存不足时候被回收), 在Activity被回收的时候我们可以调用onSaveIntanceState方法保存当前的某些状态。当用户操作当前Activity要返回前台或者我们的Activity会被重建(如:横屏操作),此时Activity与Fragment间失去联系, 我们这个时候调用getActivity()会返回为null。针对这种情况,我们可以的解决方案如下: 当Activity被重新建立的时候,onCreate的时候,我们判断savedInstanceState参数不为空的时候,即知道我们的Activity是重建的,此时我们的Fragment可能还存在,我们就没有必要重建Fragment,我们只需要通过调用FragmentManager的findFragmentByTag方法重新连接Fragment和Activity.
针对以上的解决方案,我们可以在Android系统的实现代码里找到类似的实现。我们看到从Fragment诞生开始,很多App开始用ViewPager+PagerAdapter的子类(即:FragmentPagerAdapter和FragmentStatePagerAdapter)来制作首页。在ViewPager + Fragment的首页实现组合情况下,我们不用担心Fragment里调用getActivity()为空的问题。我们看FragmentPagerAdapter的源码,我们知道FragmentPagerAdapter和FragmentStatePagerAdapter都是继承抽象基类PagerAdapter,我们重点看FragmentPagerAdapter的源码instantiateItem方法的实现:
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- if (mCurTransaction == null) {
- mCurTransaction = mFragmentManager.beginTransaction();
- }
- final long itemId = getItemId(position);
- // Do we already have this fragment?
- String name = makeFragmentName(container.getId(), itemId);
- Fragment fragment = mFragmentManager.findFragmentByTag(name);
- if (fragment != null) {
- if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
- mCurTransaction.attach(fragment);
- } else {
- fragment = getItem(position);
- if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
- mCurTransaction.add(container.getId(), fragment,
- makeFragmentName(container.getId(), itemId));
- }
- if (fragment != mCurrentPrimaryItem) {
- FragmentCompat.setMenuVisibility(fragment, false);
- FragmentCompat.setUserVisibleHint(fragment, false);
- }
- return fragment;
- }
我们从Android官方文档可以知道,instantiateItem方法的作用:The adapter is responsible for adding the view to the container given here, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup).我们知道instantiateItem会给我们制定位置返回一个页面。我们看到以下两句话:
- // Do we already have this fragment?
- String name = makeFragmentName(container.getId(), itemId);
- Fragment fragment = mFragmentManager.findFragmentByTag(name);
如果我们的Fragment存在的话,我们直接关联fragment和Activity,如果不存在的话,我们新建Fragment.
- Fragment Tansactions 和Activity的状态丢失的问题
我们的代码出现过如下异常:
- java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState
- at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
- at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
- at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
- at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
这种异常的出现是由于,在Activity的状态保存之后,尝试去提交一个FragmentTransaction。这种现象被称为活动状态丢失(Activity State Loss)。我在实际开发中,异常小概率出现的情况发生在我们切换消息和电话按钮的时候,抛出以上的异常。通过后来查找问题,发现原因由于在onSaveInstanceState()方法调用后会调用FragmentTransaction的commit方法。这个transaction将不会被记住,因为它没有在第一时间记录为这个Activity的状态的一部分。这个transaction将会丢失,可能导致UI状态不一致。此处,Android系统检测到不一致会抛出一个IllegalStateException异常。最简单的解决办法就是在除onCreate方法外的周期,尽量去忽视状态一致性的检查,我们将commit方法改为commitAllowingStateLoss。更复杂的状态的丢失解决办法参考这篇文字http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html。
本文总结会继续不定时更新,有错误的地方还望园友指正,附上Demo。