发现问题
在使用Fragment的时候经常会遇到getActivity()为null的情况,比如,在一个异步网络请求的回调中调用getActivity()就会出现空指针的问题,之前解决这个问题,都是直接加空指针判断来规避,但是这个问题并没有真正的解决。还有一种现象,如果App长时间在后台运行,再次进入App的时候,可能会出现Crash,还有Fragment会有重叠现象。这是官方Fragment库的一些自身的Bug。
假设我们的页面叫MyActivity(继承自FragmentActivity),其中用到的Fragment叫MyFragment。 出现上面这种情况时,app发生的变化如下:
1、在前面提到的几种情况下系统回收了MyActivity
2、通过onSaveInstanceState保存MyFragment的状态
3、用户再次点击进入app
4、由于MyActivity被回收,系统会重启MyActivity,根据之前保存的MyFragment的状态恢复fragment
5、MyActivity的代码逻辑中,会再次创建新的MyFragment
6、页面出现混乱,覆盖了两层的fragment。假如恢复的MyFragment使用到了getActivity()方法,会报空指针异常
查找原因
先规定一个“术语”,安卓App有一种特殊情况,就是 App运行在后台的时候,系统资源紧张的时候导致把App的资源全部回收(杀死app的进程),这时把App再从后台返回到前台时,App会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)
如果系统内存不足、切换横竖屏、App长时间在后台运行,Activity都可能会被系统回收然后重建。在系统要把App回收之前,系统会把Activity的状态会保存下来,但Fragment并不会随着Activity的回收而被回收,创建的所有Fragment会被保存到Bundle里面,从而导致Fragment丢失对应的Activity。
Activity的FragmentManager负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate
方法调用后紧接着恢复(从onAttach
生命周期开始)。
FragmentActivity的部分源码:
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable( "android:support:fragments", p);
}
...
}
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mFragments.attachActivity(this, mContainer, null);
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable("android:support:fragments");
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
}
...
mFragments.dispatchCreate();
}
解决问题
方法1:不保存fragment的状态:在MyActivity中重写onSaveInstanceState方法,将super.onSaveInstanceState(outState);注释掉,让其不再保存Fragment的状态,达到fragment随MyActivity一起销毁的目的。
方法2:重建时清除已经保存的fragment的状态:在恢复Fragment之前把Bundle里面的fragment状态数据给清除。方法如下:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 只要发生onSaveInstanceState就remove all Fragment
if(outState!=null){
outState.remove("android:support:fragments");
}
}
方法3:在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)里赋值,使用mActivity代替getActivity(),保证Fragment即使在onDetach后,仍持有Activity的引用(有引起内存泄露的风险,但是异步任务没停止的情况下,本身就可能已内存泄漏,相比Crash,这种做法“安全”些),即在BaseFragment中写:
public Activity mActivity;
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.mActivity = (Activity)context;
}