免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3671 | 回复: 2
打印 上一主题 下一主题

[Android] Android中管理多个Fragment的最佳实践,完美解决保存状态与重影问题 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2015-07-24 14:09 |只看该作者 |倒序浏览
自从在Android 3.0引入Fragment以来,它被使用的频率也随之增多。Fragment带来的好处不言而喻,解决了不同屏幕分辨率的动态和灵活UI设计。但是在Activity管理多个Fragment中,通常会遇到这些问题:

1、Fragment的状态保存

2、Fragment的重影

当然,这些问题也一直出现我的开发过程中,虽然有时候通过各种手段也能解决一些问题,但是总是同时完美解决这两个问题。近来因为项目需要,查阅了很多官方资料(Android官方资料也慢慢有中文资料了,我大Google果然是Don't be evil,扯远了~~),终于彻底解决了这些问题。

设备:nexus 5

条件:

1、打开“不保留活动”(开发者选项里,主要用于模拟Activity被及时回收)

2、关闭“不保留活动”(正常状态下)

结果:目前没发现问题,由于设备有限,大家如果发现在其他设备上有问题,请在下方回帖!



首先我先来解释下上面问题出现的原因:

1、有时候,我们需要在多个Fragment间切换,并且保存每个Fragment的状态。官方的方法是使用replace()来替换Fragment,但是replace()的调用会导致Fragment的onCreteView()被调用,所以切换界面时会无法保存当前的状态。因此一般采用add()、hide()与show()配合,来达到保存Fragment的状态。以下为代码片段:
  1. private void setTabSelection(int position) {
  2.         //记录position
  3.         this.position = position;
  4.         //更改底部导航栏按钮状态
  5.         changeButtonStatus(position);
  6.         FragmentTransaction transaction = fragmentManager.beginTransaction();
  7.         // 先隐藏掉所有的Fragment,以防止有多个Fragment显示在界面上的情况
  8.         hideFragments(transaction);
  9.         switch (position) {
  10.             case TAB_HOME:
  11.                 btnHomePager.setSelected(true);
  12.                 btnShoppingCart.setSelected(false);
  13.                 btnMine.setSelected(false);
  14.                 if (homeFragment == null) {
  15.                     homeFragment = new HomePagerFragment();
  16.                     transaction.add(R.id.fragment_container, homeFragment);
  17.                 } else {
  18.                     transaction.show(homeFragment);
  19.                 }
  20.                 break;
  21.             case TAB_SHOP:
  22.                 btnHomePager.setSelected(false);
  23.                 btnShoppingCart.setSelected(true);
  24.                 btnMine.setSelected(false);
  25.                 if (shoppingFragment == null) {
  26.                     shoppingFragment = new ShoppingCartFragment();
  27.                     transaction.add(R.id.fragment_container, shoppingFragment);
  28.                 } else {
  29.                     transaction.show(shoppingFragment);
  30.                 }
  31.                 break;
  32.             case TAB_MINE:
  33.                 btnHomePager.setSelected(false);
  34.                 btnShoppingCart.setSelected(false);
  35.                 btnMine.setSelected(true);
  36.                 if (mineFragment == null) {
  37.                     mineFragment = new MineFragment();
  38.                     transaction.add(R.id.fragment_container, mineFragment);
  39.                 } else {
  40.                     transaction.show(mineFragment);
  41.                 }
  42.                 break;
  43.         }
  44.         transaction.commitAllowingStateLoss();
  45.     }
复制代码
2、第二个问题的出现正是因为使用了Fragment的状态保存,当系统内存不足,Fragment的宿主Activity回收的时候,Fragment的实例并没有随之被回收。Activity被系统回收时,会主动调用onSaveInstance()方法来保存视图层(View Hierarchy),所以当Activity通过导航再次被重建时,之前被实例化过的Fragment依然会出现在Activity中,然而从上述代码中可以明显看出,再次重建了新的Fragment,综上这些因素导致了多个Fragment重叠在一起。



我尝试了很多种方法去解决这个问题,比如:

在onSaveInstance()里面去remove()所有非空的Fragment,然后在onRestoreInstanceState()中去再次按照问题一的方式创建Activity。当我处于打开“不保留活动”的时候,效果非常令人满意,然而当我关闭“不保留活动”的时候,问题却出现了。当转跳到其他Activity、打开多任务窗口、使用Home回到主屏幕再返回时,发现根本没有Fragment了,一篇空白。

于是跟踪下去,我调查了onSaveInstanceState()与onRestoreInstanceState()这两个方法。原本以为只有在系统因为内存回收Activity时才会调用的onSaveInstanceState(),居然在转跳到其他Activity、打开多任务窗口、使用Home回到主屏幕这些操作中也被调用,然而onRestoreInstanceState()并没有在再次回到Activity时被调用。而且我在onResume()发现之前的Fragment只是被移除,并不是空,所以就算你在onResume()中执行问题一中创建的Fragment的方法,同样无济于事。所以通过remove()宣告失败。

接着通过调查资料发现Activity中的onSaveInstanceState()里面有一句super.onRestoreInstanceState(savedInstanceState),Google对于这句话的解释是“Always call the superclass so it can save the view hierarchy state”,大概意思是“总是执行这句代码来调用父类去保存视图层的状态”。其实到这里大家也就明白了,就是因为这句话导致了重影的出现,于是我删除了这句话,然后onCreate()与onRestoreInstanceState()中同时使用问题一中的创建Fragment方法,然后再通过保存切换的状态,发现结果非常完美。代码如下:
  1. //记录Fragment的位置
  2.     private int position = 0;

  3.     @Override
  4.     protected void onCreate(Bundle savedInstanceState) {
  5.         super.onCreate(savedInstanceState);
  6.         setContentView(R.layout.activity_index);

  7.         setTabSelection(position);
  8.     }

  9.     @Override
  10.     protected void onRestoreInstanceState(Bundle savedInstanceState) {
  11.         position = savedInstanceState.getInt("position");
  12.         setTabSelection(position);
  13.         super.onRestoreInstanceState(savedInstanceState);
  14.     }

  15.     @Override
  16.     protected void onSaveInstanceState(Bundle outState) {
  17.         //记录当前的position
  18.         outState.putInt("position", position);
  19.     }
复制代码
记录于此,希望能帮助到一些正遇到这种问题的朋友!

论坛徽章:
80
20周年集字徽章-庆
日期:2020-10-28 14:09:1215-16赛季CBA联赛之北京
日期:2020-10-28 13:32:5315-16赛季CBA联赛之北控
日期:2020-10-28 13:32:4815-16赛季CBA联赛之天津
日期:2020-10-28 13:13:35黑曼巴
日期:2020-10-28 12:29:1520周年集字徽章-周	
日期:2020-10-31 15:10:0720周年集字徽章-20	
日期:2020-10-31 15:10:07ChinaUnix元老
日期:2015-09-29 11:56:3020周年集字徽章-年
日期:2020-10-28 14:14:56
2 [报告]
发表于 2015-07-24 15:06 |只看该作者
这个挺好的。很受用

论坛徽章:
59
2015七夕节徽章
日期:2015-08-24 11:17:25ChinaUnix专家徽章
日期:2015-07-20 09:19:30每周论坛发贴之星
日期:2015-07-20 09:19:42ChinaUnix元老
日期:2015-07-20 11:04:38荣誉版主
日期:2015-07-20 11:05:19巳蛇
日期:2015-07-20 11:05:26CU十二周年纪念徽章
日期:2015-07-20 11:05:27IT运维版块每日发帖之星
日期:2015-07-20 11:05:34操作系统版块每日发帖之星
日期:2015-07-20 11:05:36程序设计版块每日发帖之星
日期:2015-07-20 11:05:40数据库技术版块每日发帖之星
日期:2015-07-20 11:05:432015年辞旧岁徽章
日期:2015-07-20 11:05:44
3 [报告]
发表于 2015-08-11 15:39 |只看该作者
我一般不使用这个布局。麻烦。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP