仿QQ菜单栏:消息,电话菜单

在实际项目开发使用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为空的问题,实现界面如下:
 
[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方法的作用:Create the page for the given position. 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
原文地址:https://www.cnblogs.com/wangoublog/p/6272765.html