您的位置:首页 > 移动开发 > Android开发

我踩到的关于Fragment 状态的保存和恢复的坑

2016-08-14 23:44 417 查看
在进行项目开发的时候遇到了一个奇怪的坑,在Activity和Fragment传递对象的时候已经对对象进行了判空处理,但是在Fabric统计上还是出现了“NullPointException”,我们在代码中的具体实现是在Activity里将对象注入Fragment中:

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被销毁了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息