转载自: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为空的问题,实现界面如下:
 
  1. public class SwitchActivity extends FragmentActivity {
  2. private Button btn_message,btn_call;
  3. private CallFragment callFragment;
  4. private MessageFragment messageFragment;
  5. public static final int MESSAGE_FRAGMENT_TYPE = 1;
  6. public static final int CALL_FRAGMENT_TYPE = 2;
  7. public int currentFragmentType = -1;
  8. @Override
  9. protected void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. this.requestWindowFeature(Window.FEATURE_NO_TITLE);
  12. setContentView(R.layout.activity_switch);
  13. btn_message = (Button)findViewById(R.id.btn_message);
  14. btn_call = (Button)findViewById(R.id.btn_call);
  15. btn_message.setOnClickListener(onClicker);
  16. btn_call.setOnClickListener(onClicker);
  17. FragmentManager fragmentManager = getSupportFragmentManager();
  18. if (savedInstanceState != null) {
  19. //当savedInstanceState不为空的时候,说明当前的Activity是被回收后重建,
  20. //我们重新建立Fragment和Activity的联系。
  21. int type = savedInstanceState.getInt("currentFragmentType");
  22. messageFragment = (MessageFragment)fragmentManager.findFragmentByTag("message");
  23. callFragment = (CallFragment)fragmentManager.findFragmentByTag("call");
  24. if(type > 0)
  25. loadFragment(type);
  26. } else {
  27. FragmentTransaction transaction = fragmentManager
  28. .beginTransaction();
  29. Fragment mainFragment = fragmentManager.findFragmentByTag("message");
  30. if (mainFragment != null) {
  31. transaction.replace(R.id.fl_content, mainFragment);
  32. transaction.commit();
  33. } else {
  34. loadFragment(MESSAGE_FRAGMENT_TYPE);
  35. }
  36. }
  37. }
  38. /**
  39. * 当某种原因Activity被销毁回收掉(如:App进入后台运行或者Activity压入任务栈内存不足时候被回收),
  40. * onSaveIntanceState方法保存当前的状态。当用户操作当前Activity要返回前台或者
  41. * 我们的Activity会被重建(如:横屏操作),此时Activity与Fragment间失去联系,
  42. * 我们这个时候调用getActivity()会返回为null
  43. */
  44. @Override
  45. protected void onSaveInstanceState(Bundle outState) {
  46. super.onSaveInstanceState(outState);
  47. outState.putInt("lastFragmentTag", currentFragmentType);
  48. }
  49. private void switchFragment(int type) {
  50. switch (type) {
  51. case MESSAGE_FRAGMENT_TYPE:
  52. loadFragment(MESSAGE_FRAGMENT_TYPE);
  53. break;
  54. case CALL_FRAGMENT_TYPE:
  55. loadFragment(CALL_FRAGMENT_TYPE);
  56. break;
  57. }
  58. }
  59. private void loadFragment(int type) {
  60. FragmentManager fragmentManager = getSupportFragmentManager();
  61. FragmentTransaction transaction = fragmentManager.beginTransaction();
  62. if (type == CALL_FRAGMENT_TYPE) {
  63. if (callFragment == null) {
  64. callFragment = new CallFragment();
  65. transaction.add(R.id.fl_content, callFragment, "zhishi");
  66. } else {
  67. transaction.show(callFragment);
  68. }
  69. if (messageFragment != null) {
  70. transaction.hide(messageFragment);
  71. }
  72. currentFragmentType = MESSAGE_FRAGMENT_TYPE;
  73. } else {
  74. if (messageFragment == null) {
  75. messageFragment = new MessageFragment();
  76. transaction.add(R.id.fl_content, messageFragment, "wenda");
  77. } else {
  78. transaction.show(messageFragment);
  79. }
  80. if (callFragment != null) {
  81. transaction.hide(callFragment);
  82. }
  83. currentFragmentType = CALL_FRAGMENT_TYPE;
  84. }
  85. transaction.commitAllowingStateLoss();
  86. }
  87. private OnClickListener onClicker = new OnClickListener() {
  88. @Override
  89. public void onClick(View v) {
  90. switch (v.getId()) {
  91. case R.id.btn_message:
  92. btn_message.setTextColor(Color.parseColor("#df3031"));
  93. btn_call.setTextColor(Color.WHITE);
  94. btn_message.setBackgroundResource(R.drawable.baike_btn_pink_left_f_96);
  95. btn_call.setBackgroundResource(R.drawable.baike_btn_trans_right_f_96);
  96. switchFragment(MESSAGE_FRAGMENT_TYPE);
  97. break;
  98. case R.id.btn_call:
  99. btn_message.setTextColor(Color.WHITE);
  100. btn_call.setTextColor(Color.parseColor("#df3031"));
  101. btn_message.setBackgroundResource(R.drawable.baike_btn_trans_left_f_96);
  102. btn_call.setBackgroundResource(R.drawable.baike_btn_pink_right_f_96);
  103. switchFragment(CALL_FRAGMENT_TYPE);
  104. break;
  105. }
  106. }
  107. };
  108. }
从以上代码我们知道,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方法的实现:
  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会给我们制定位置返回一个页面。我们看到以下两句话:
  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的状态丢失的问题
我们的代码出现过如下异常:
 
  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

仿QQ菜单栏:消息,电话菜单的更多相关文章

  1. Android 高仿QQ滑动弹出菜单标记已读、未读消息

    在上一篇博客<Android 高仿微信(QQ)滑动弹出编辑.删除菜单效果,增加下拉刷新功能>里,已经带着大家学习如何使用SwipeMenuListView这一开源库实现滑动列表弹出菜单,接 ...

  2. Android高仿qq及微信底部菜单的几种实现方式

    最近项目没那么忙,想着开发app的话,有很多都是重复,既然是重复的,那就没有必要每次都去写,所以就想着写一个app通用的基本框架,这里说的框架不是什么MVC,MVP,MVVM这种,而是app开发的通用 ...

  3. 【WP8】仿QQ提示消息

    WP版的QQ提示消息的时候从顶部滑入,3秒后从顶部滑出,本文模仿该效果实现一个MessageToastManager类用于显示提示消息 思路很简单,就是动画而已,支持配置颜色和回掉 // ****** ...

  4. jquery-模仿qq提示消息

    ( function() { var ua = navigator.userAgent.toLowerCase(); var is = (ua.match(/\b(chrome|opera|safar ...

  5. Android 仿QQ首页的消息和电话的切换,首页的头部(完全用布局控制)

    Android 仿QQ首页的消息和电话的切换,首页的头部(完全用布局控制) 首先贴上七个控制布局代码 1.title_text_sel.xml 字体颜色的切换 放到color文件夹下面 <?xm ...

  6. iOS天气动画、高仿QQ菜单、放京东APP、高仿微信、推送消息等源码

    iOS精选源码 TYCyclePagerView iOS上的一个无限循环轮播图组件 iOS高仿微信完整项目源码 想要更简单的推送消息,看本文就对了 ScrollView嵌套ScrolloView解决方 ...

  7. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  8. 史上最简单,一步集成侧滑(删除)菜单,高仿QQ、IOS。

    重要的话 开头说,not for the RecyclerView or ListView, for the Any ViewGroup. 本控件不依赖任何父布局,不是针对 RecyclerView. ...

  9. Android仿QQ ios dialog,仿QQ退出向上菜单

    Android仿QQ ios dialog,仿QQ退出向上菜单 EasyDialog两种模式 仿QQ退出向上菜单,自己定义向上菜单              github地址:https://gith ...

随机推荐

  1. BZOJ5487: [Usaco2018 Dec]Cowpatibility

    Description 研究证明,有一个因素在两头奶牛能否作为朋友和谐共处这方面比其他任何因素都来得重要--她们是不是喜欢同 一种口味的冰激凌!Farmer John的N头奶牛(2≤N≤50,000) ...

  2. vivado 创建PS工程

    前言 本文简要介绍在vivado中创建PS工程.单纯使用zynq芯片的PS部分就像使用普通ARM芯片一样,只是多了建立Zynq硬件系统这一个步骤.vivado创建PL工程参见此处 新建工程 与viva ...

  3. Linux 软连接 (ln命令)

    这是linux中一个非常重要命令.它的功能是为某一个文件在另外一个位置建立一个同不的链接,这个命令最常用的参数是-s,具体用法是:ln -s 源文件 目标文件. 当我们需要在不同的目录,用到相同的文件 ...

  4. Centos 6.8安装ideaIU-2017.2.6-no-jdk

    参考资料: (一)https://www.jetbrains.com/help/idea/2017.2/intellij-idea-help.pdf  (链接: https://pan.baidu.c ...

  5. 关于AndroidStudio 经常弹出TortoiseSVN 同步的解决办法

    我的AndroidStudio在使用时是从TortoiseSVN的文件夹下直接打开的 但是由于svn自己的特点每改一个文件就要跳出来一个svn的对话框 体验极其难受!砍人的心都有了 网上的解决办法都是 ...

  6. Log4Net 添加自定义字段并保存到数据库

    Log4Net是常用的功能强大的日志插件,该插件提供了几个默认字段 大家可能都用过Log4Net插件来记录日志,该插件默认提供了这几个字段@log_date, @thread, @log_level, ...

  7. idea启动springboot项目 报错:java.lang.NoSuchMethodError: javax.servlet.ServletContext.getClassLoader()Ljava/lang/ClassLoader;

    有一次启动springboot项目的时候,报了一个非常奇怪的错误,说是找不到servletContext,springboot不是自带tomcat的吗? 在网上找了好久,说是用以下方式解决. 解决方式 ...

  8. 【百度地图API】如何获取行政区域的边界?(转载)

    转自:http://www.cnblogs.com/milkmap/archive/2012/04/11/2442430.html 摘要:以前教过大家如何自行获取行政区域,或者自定义获取一个区域的边界 ...

  9. 【Oracle】【8】大批量update某个字段

    正文: 需实现:将A表的某个字段的值复制到B表中 我们一般会这样写:UPDATE B SET B.NAME = (SELECT A.NAME FROM A WHERE A.NO = B.NO) 出现的 ...

  10. 第一次作业——WorkCount

    项目地址:https://gitee.com/yangfj/wordcount_project 1.软件需求分析: 撰写PSP表格: PSP2.1 PSP阶段 预估耗时 (分钟) 实际耗时 (分钟) ...