在实际项目开发使用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为空的问题,实现界面如下:
 
仿QQ菜单栏:消息,电话菜单仿QQ菜单栏:消息,电话菜单
[java] view plain copy
 
  1. public class SwitchActivity extends FragmentActivity {  
  2.   
  3.     private Button btn_message,btn_call;  
  4.     private CallFragment callFragment;  
  5.     private MessageFragment messageFragment;  
  6.     public static final int MESSAGE_FRAGMENT_TYPE = 1;  
  7.     public static final int CALL_FRAGMENT_TYPE = 2;  
  8.     public int currentFragmentType = -1;  
  9.       
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         this.requestWindowFeature(Window.FEATURE_NO_TITLE);  
  14.         setContentView(R.layout.activity_switch);  
  15.           
  16.         btn_message = (Button)findViewById(R.id.btn_message);  
  17.         btn_call = (Button)findViewById(R.id.btn_call);  
  18.         btn_message.setOnClickListener(onClicker);  
  19.         btn_call.setOnClickListener(onClicker);  
  20.         FragmentManager fragmentManager = getSupportFragmentManager();  
  21.         if (savedInstanceState != null) {   
  22.             //当savedInstanceState不为空的时候,说明当前的Activity是被回收后重建,  
  23.             //我们重新建立Fragment和Activity的联系。  
  24.             int type = savedInstanceState.getInt("currentFragmentType");  
  25.             messageFragment = (MessageFragment)fragmentManager.findFragmentByTag("message");  
  26.             callFragment = (CallFragment)fragmentManager.findFragmentByTag("call");  
  27.             if(type > 0)  
  28.                 loadFragment(type);  
  29.         } else {  
  30.             FragmentTransaction transaction = fragmentManager  
  31.                     .beginTransaction();  
  32.             Fragment mainFragment = fragmentManager.findFragmentByTag("message");  
  33.             if (mainFragment != null) {  
  34.                 transaction.replace(R.id.fl_content, mainFragment);  
  35.                 transaction.commit();  
  36.             } else {  
  37.                 loadFragment(MESSAGE_FRAGMENT_TYPE);  
  38.             }  
  39.         }  
  40.     }  
  41.     /** 
  42.      * 当某种原因Activity被销毁回收掉(如:App进入后台运行或者Activity压入任务栈内存不足时候被回收), 
  43.      * onSaveIntanceState方法保存当前的状态。当用户操作当前Activity要返回前台或者 
  44.      * 我们的Activity会被重建(如:横屏操作),此时Activity与Fragment间失去联系, 
  45.      * 我们这个时候调用getActivity()会返回为null 
  46.      */  
  47.     @Override  
  48.     protected void onSaveInstanceState(Bundle outState) {  
  49.         super.onSaveInstanceState(outState);  
  50.         outState.putInt("lastFragmentTag", currentFragmentType);  
  51.     }  
  52.       
  53.     private void switchFragment(int type) {  
  54.         switch (type) {  
  55.         case MESSAGE_FRAGMENT_TYPE:  
  56.             loadFragment(MESSAGE_FRAGMENT_TYPE);  
  57.             break;  
  58.         case CALL_FRAGMENT_TYPE:  
  59.             loadFragment(CALL_FRAGMENT_TYPE);  
  60.             break;  
  61.         }  
  62.     }  
  63.   
  64.     private void loadFragment(int type) {  
  65.         FragmentManager fragmentManager = getSupportFragmentManager();  
  66.         FragmentTransaction transaction = fragmentManager.beginTransaction();  
  67.         if (type == CALL_FRAGMENT_TYPE) {  
  68.             if (callFragment == null) {  
  69.                 callFragment = new CallFragment();  
  70.                 transaction.add(R.id.fl_content, callFragment, "zhishi");  
  71.             } else {  
  72.                 transaction.show(callFragment);  
  73.             }  
  74.             if (messageFragment != null) {  
  75.                 transaction.hide(messageFragment);  
  76.             }  
  77.             currentFragmentType = MESSAGE_FRAGMENT_TYPE;  
  78.         } else {  
  79.             if (messageFragment == null) {  
  80.                 messageFragment = new MessageFragment();  
  81.                 transaction.add(R.id.fl_content, messageFragment, "wenda");  
  82.             } else {  
  83.                 transaction.show(messageFragment);  
  84.             }  
  85.             if (callFragment != null) {  
  86.                 transaction.hide(callFragment);  
  87.             }  
  88.             currentFragmentType = CALL_FRAGMENT_TYPE;  
  89.         }  
  90.         transaction.commitAllowingStateLoss();  
  91.     }  
  92.       
  93.     private OnClickListener onClicker = new OnClickListener() {  
  94.         @Override  
  95.         public void onClick(View v) {  
  96.             switch (v.getId()) {  
  97.             case R.id.btn_message:  
  98.                 btn_message.setTextColor(Color.parseColor("#df3031"));  
  99.                 btn_call.setTextColor(Color.WHITE);  
  100.                 btn_message.setBackgroundResource(R.drawable.baike_btn_pink_left_f_96);  
  101.                 btn_call.setBackgroundResource(R.drawable.baike_btn_trans_right_f_96);  
  102.                 switchFragment(MESSAGE_FRAGMENT_TYPE);  
  103.                 break;  
  104.             case R.id.btn_call:  
  105.                 btn_message.setTextColor(Color.WHITE);  
  106.                 btn_call.setTextColor(Color.parseColor("#df3031"));  
  107.                 btn_message.setBackgroundResource(R.drawable.baike_btn_trans_left_f_96);  
  108.                 btn_call.setBackgroundResource(R.drawable.baike_btn_pink_right_f_96);  
  109.                 switchFragment(CALL_FRAGMENT_TYPE);  
  110.                 break;  
  111.             }  
  112.         }  
  113.     };  
  114. }  
从以上代码我们知道,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方法的实现:
[java] view plain copy
 
  1. @Override  
  2.     public Object instantiateItem(ViewGroup container, int position) {  
  3.         if (mCurTransaction == null) {  
  4.             mCurTransaction = mFragmentManager.beginTransaction();  
  5.         }  
  6.         final long itemId = getItemId(position);  
  7.         // Do we already have this fragment?  
  8.         String name = makeFragmentName(container.getId(), itemId);  
  9.         Fragment fragment = mFragmentManager.findFragmentByTag(name);  
  10.         if (fragment != null) {  
  11.             if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);  
  12.             mCurTransaction.attach(fragment);  
  13.         } else {  
  14.             fragment = getItem(position);  
  15.             if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);  
  16.             mCurTransaction.add(container.getId(), fragment,  
  17.                     makeFragmentName(container.getId(), itemId));  
  18.         }  
  19.         if (fragment != mCurrentPrimaryItem) {  
  20.             FragmentCompat.setMenuVisibility(fragment, false);  
  21.             FragmentCompat.setUserVisibleHint(fragment, false);  
  22.         }  
  23.         return fragment;  
  24.     }  
我们从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会给我们制定位置返回一个页面。我们看到以下两句话:
[java] view plain copy
 
  1. // Do we already have this fragment?  
  2.         String name = makeFragmentName(container.getId(), itemId);  
  3.         Fragment fragment = mFragmentManager.findFragmentByTag(name);  
如果我们的Fragment存在的话,我们直接关联fragment和Activity,如果不存在的话,我们新建Fragment.
  • Fragment Tansactions 和Activity的状态丢失的问题
我们的代码出现过如下异常:
 
[plain] view plain copy
 
  1. java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState  
  2.     at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)  
  3.     at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)  
  4.     at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)  
  5.     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

相关文章: