我踩到的关于Fragment 状态的保存和恢复的坑
2016-08-14 23:44
417 查看
在进行项目开发的时候遇到了一个奇怪的坑,在Activity和Fragment传递对象的时候已经对对象进行了判空处理,但是在Fabric统计上还是出现了“NullPointException”,我们在代码中的具体实现是在Activity里将对象注入Fragment中:
然后在Fragment的方法里获取传入的对象:
看起来似乎没有什么问题,我在Activity中已经判了product是否为空了,不为空的时候就创建Fragment的对象,然后将product对象传进去。但是我在Fragment里面使用product.productUrl时Fabric上却报出TProductExtension对象是空,虽然这个出错的概率在Fabric上显示占比比较小,但是我们还是要将这个bug给fix掉。
最开始想的办法是在onActivityCreated中获取product对象时再一次进行判空处理,即:
这样虽然能使这段代码不再抛出”NullPointException”,但是我们并没有找到错误的根源在哪里,并且这样的判断也会引出其他的问题,例如当product再一次是空的时候,Fragment连View都不会进行显示。所以这个方法行不通。
那么,问题到底在哪呢?既然我们传入的方式是没有问题的,那么问题会不会出现在Fragment或者Activity自身呢?我们开始试着从Fragment生命周期下手。我们在Fragment里重写了onResume方法,打上了断点进行调试,然后神奇的事情发生了,当我从当前Activity跳转到下一个Activity再返回回来时,Fragment里的onResume方法居然被调用了两次。然后我去Activity看了创建Fragment对象的方法,loadData是在Activity的onCreate方法里,我在
最后发现,原来罪魁祸首就是onCreate的参数
当用户要离开Activity并在Activity意外销毁时向其传递将保存的 Bundle 对象时,系统会调用此方法。
如果系统必须稍后重新创建Activity实例,它会将相同的 Bundle 对象同时传递给 onRestoreInstanceState()和 onCreate() 方法。
![](https://img-blog.csdn.net/20160814225053542)
以上是来自Android官方开发文档解释。
而为了证实到底是不是这个原因,我重写了onResume、onSaveInstanceState、onRestoreInstanceState、onPause方法进行断点调试从当前Activity跳转到下一个Activity再返回,得出的这几个方法的调用顺序分别是:onPause、onSaveInstanceState、onStop、onCreate、onRestoreInstanceState、onResume,而在onSaveInstanceState方法里会将我当前Activity内的Fragment存入HashMap中再存到Bundle对象中,然后当我Activity被销毁后返回回来时,系统会从onCreate方法和onRestoreInstanceState方法中将我们之前存入的Fragment取出来进行显示。
![](https://img-blog.csdn.net/20160814232951439)
这是断点调试onCreate方法时获取到的Bundle对象,在mActive这个map中就存有我的Fragment等信息。
这样也就导致了我返回Activity后在onCreate方法里调用loadData方法new了一个Fragment,但Bundle对象中还保存了我的Fragment对象,在onCreate方法被调用时被取出,所以Fragment的onResume方法被调用了两次。而导致空指针异常的情况是,恢复的Fragment并没有走数据传递那一步,导致了我在Fragment里取到的product对象是空。
知道了这个原因,那问题就好解决了。只要onCreate方法调用的loadData里判断onSaveInstanceState是否为空,如果为空的话再创建Fragment的对象,如果不为空的话就让系统帮我进行Fragment的显示。
具体代码如下:
一般来说我们都应该注意Activity销毁时将必要的数据进行保存,保存数据写在onSaveInstanceState方法里,取出数据则写在onRestoreInstanceState方法中。
ps:以上都是在开启Android开发者选项的“不保留活动”这个选项后实践的,如果没有开启这个选项很难保证跳转下一个Activity时,上一个Activity被销毁了。
public void loadData(){ if(product != null){ ProductDetailBottomLayoutFragment bottomLayoutFragment = new ProductDetailBottomLayoutFragment(); Bundle bundle = new Bundle(); bundle.putSerializable("product", product); bottomLayoutFragment.setArguments(bundle); transaction.replace(R.id.product_detail_bottom_fragment, bottomLayoutFragment).commit(); } }
然后在Fragment的方法里获取传入的对象:
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); product = (TProductExtension) getArguments().getSerializable("product"); if (getView() != null) { ... getIsFavorite(product.productUrl); ... } }
看起来似乎没有什么问题,我在Activity中已经判了product是否为空了,不为空的时候就创建Fragment的对象,然后将product对象传进去。但是我在Fragment里面使用product.productUrl时Fabric上却报出TProductExtension对象是空,虽然这个出错的概率在Fabric上显示占比比较小,但是我们还是要将这个bug给fix掉。
最开始想的办法是在onActivityCreated中获取product对象时再一次进行判空处理,即:
if (product != null && getView() != null) { ... getIsFavorite(product.productUrl); ... }
这样虽然能使这段代码不再抛出”NullPointException”,但是我们并没有找到错误的根源在哪里,并且这样的判断也会引出其他的问题,例如当product再一次是空的时候,Fragment连View都不会进行显示。所以这个方法行不通。
那么,问题到底在哪呢?既然我们传入的方式是没有问题的,那么问题会不会出现在Fragment或者Activity自身呢?我们开始试着从Fragment生命周期下手。我们在Fragment里重写了onResume方法,打上了断点进行调试,然后神奇的事情发生了,当我从当前Activity跳转到下一个Activity再返回回来时,Fragment里的onResume方法居然被调用了两次。然后我去Activity看了创建Fragment对象的方法,loadData是在Activity的onCreate方法里,我在
ProductDetailBottomLayoutFragment bottomLayoutFragment = new ProductDetailBottomLayoutFragment();这段代码打上断点调试,却发现这段代码却只运行了一次,那么另外一次onResume是谁调用的,又是在哪调用的呢?
最后发现,原来罪魁祸首就是onCreate的参数
Bundle savedInstanceState, 没错就是他。我们都知道,Activity有自己的一套状态保存机制。
当用户要离开Activity并在Activity意外销毁时向其传递将保存的 Bundle 对象时,系统会调用此方法。
如果系统必须稍后重新创建Activity实例,它会将相同的 Bundle 对象同时传递给 onRestoreInstanceState()和 onCreate() 方法。
以上是来自Android官方开发文档解释。
而为了证实到底是不是这个原因,我重写了onResume、onSaveInstanceState、onRestoreInstanceState、onPause方法进行断点调试从当前Activity跳转到下一个Activity再返回,得出的这几个方法的调用顺序分别是:onPause、onSaveInstanceState、onStop、onCreate、onRestoreInstanceState、onResume,而在onSaveInstanceState方法里会将我当前Activity内的Fragment存入HashMap中再存到Bundle对象中,然后当我Activity被销毁后返回回来时,系统会从onCreate方法和onRestoreInstanceState方法中将我们之前存入的Fragment取出来进行显示。
这是断点调试onCreate方法时获取到的Bundle对象,在mActive这个map中就存有我的Fragment等信息。
这样也就导致了我返回Activity后在onCreate方法里调用loadData方法new了一个Fragment,但Bundle对象中还保存了我的Fragment对象,在onCreate方法被调用时被取出,所以Fragment的onResume方法被调用了两次。而导致空指针异常的情况是,恢复的Fragment并没有走数据传递那一步,导致了我在Fragment里取到的product对象是空。
知道了这个原因,那问题就好解决了。只要onCreate方法调用的loadData里判断onSaveInstanceState是否为空,如果为空的话再创建Fragment的对象,如果不为空的话就让系统帮我进行Fragment的显示。
具体代码如下:
public void loadData(Bundle savedInstanceState){ if(product != null){ if(savedInstanceState == null){ ProductDetailBottomLayoutFragment bottomLayoutFragment = new ProductDetailBottomLayoutFragment(); Bundle bundle = new Bundle(); bundle.putSerializable("product", product); bottomLayoutFragment.setArguments(bundle); transaction.replace(R.id.product_detail_bottom_fragment, bottomLayoutFragment).commit(); } } }
一般来说我们都应该注意Activity销毁时将必要的数据进行保存,保存数据写在onSaveInstanceState方法里,取出数据则写在onRestoreInstanceState方法中。
ps:以上都是在开启Android开发者选项的“不保留活动”这个选项后实践的,如果没有开启这个选项很难保证跳转下一个Activity时,上一个Activity被销毁了。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories