Android Do not keep activities选项分析
Android Do not keep activities选项分析
Developer Options里面有一项:
Do not keep activities -> 不保留Activities.
默认是不开启的.
当开启之后,用户离开后即销毁每个Activity.
相关背景知识: task, back stack和低内存时的系统行为
当用户开启一个task,里面的activities都会被保存在这个栈的back stack中.
当前的activity在栈顶并且拥有焦点,之前的activities都被压入栈中,处于stopped的状态.
Android系统后台是可以保存多个task的.
当用户开启另一个新的task或者Home键退回到桌面的时候,task将会变为在后台运行.
当task在后台时,其中所有的activities都是停止的(stopped),其back stack保持不变,这个task只是失去了焦点.
task可以回到前台状态,即顶端的activity会被resume,这样用户就可以回到他们离开的现场.
清理back stack:
如果用户离开一个task很长时间,系统将会清理task中的所有activity, 只保留根activity, 当用户返回到这个task的时候, 只有根activity会被恢复. 这个行为也可以通过一些activity的属性来控制.
系统可能会kill掉activity.
当activity停止时(stopped), 系统的默认行为是保存它的状态, 但是当系统的内存不足时, 可能会kill掉activity, 这时候activity的信息会丢失.
即便是kill掉了activity, 系统仍然知道这个activity在back stack中的位置, 当这个activity需要返回到栈顶的时候, 系统会重建(recreate)它, 而不是像之前一样恢复(resume)它.
重建的时候有一些数据会丢失,比如一些成员变量的值.
为了不丢失用户的工作, 可以使用回调onSaveInstanceState()来主动地保存数据, 以防activity被系统销毁而需要重建.
Do not keep activities开关作用
当这个开关被打开,所有被stop的activity都会被立即销毁.
打开Do not keep activities开关, 可以很方便地模拟出内存不足时,系统kill activity的极端情况.
打开此开关可以用来检验应用是否在activity停止时保存了需要的数据, 即是否可以在activity回到前台时复原数据, 甚至可以检查出一些没有做好保护的异常.
所以在开发应用的时候打开这个开关做一些测试和保护是很有益的.
但是这个开关只处理了Activity, 对于系统在内存不足时杀死Service的情况并不能模拟出来.
Do not keep activities开关对Activity的生命周期影响
做了一个小实验对比这个开关打开和关闭的情况:
实验很简单,有一个Activity A, 里面有一个button,点击即打开Activity B, B可以finish自己, 再回到A.
即A -> B -> A.
两个Activity的launch mode都是standard, 即默认情况.
打印出它们的生命周期回调函数.
正常情况下是这样的:
可以看到当B打开以后, A只是onStop()了; 当B finish自己, A重新调用了onRestart()->onResume().
当Do not keep activities开关打开,同一个程序,输出如下:
可以看到当B打开以后, A不仅调用了onStop(), 还调用了onDestroy(), 当B finish自己,再返回A的时候, A重新调用了onCreate() -> onResume(), 而并不是onRestart().
这说明activity A已经被重新创建, 不再是之前的那个实例, 所有未在onSaveInstanceState()回调方法中保存的数据都已经丢失.
这里可以参见Activity的生命周期: http://www.cnblogs.com/mengdd/archive/2012/12/01/2797784.html
Do not keep activities开关对Fragment的生命周期影响
先来复习一下Fragment的生命周期:
关于Fragment的生命周期可以查看: http://developer.android.com/guide/components/fragments.html#Lifecycle
我写了一个Demo, 一个自定义的Fragment A,一开始就加在Activity的布局里.
Fragment A和Activity的生命周期回调函数调用顺序如图:
可以看到,在创建阶段,Activity的回调总是先调用的,Fragment的几个回调后调用.
而在暂停到停止和销毁阶段,总是先调用Fragment的回调,再调用同阶段的Activity回调.
可以总结为: Activity是个早出晚归的勤劳老大哥.
通过点击Activity中的UI动态添加的另一个Fragment B, 则是在添加之后一股脑调用Fragment的回调直到它在最前面:
好啦,复习完毕,现在来说Do not keep activities开关.
我的操作步骤如下:
1.打开Activity, FragmentA写在activity的布局里,所以自动添加;
2.点击toggle添加FragmentB;
3.Home键退出;
4.再打开应用,应用应该自动返回到离开前的Activity.
正常状况下Log如下:
而当Do not keep activities开关开启之后,Log的前半部分是一样的,在此略去(太长了), 后半部分如下:
可见一旦Home键退出,Fragment被销毁,再次进来时重建,并且恢复到原来所在的布局中去.
只是因为Activity在onSaveInstanceState()中保存了View和Fragment的状态, 在onRestoreInstanceState()恢复了这些状态.
可以看到log中的android:viewHierarchyState是View的状态
android:fragments=android.app.FragmentManagerState@42ac6328是Fragment的状态.
所以在override这两个方法保存其他数据时, 一般都需要调用super的方法.
需要处理的问题:
开始的时候Demo里动态添加FragmentB用了一个ToggleButton:
@OnCheckedChanged(R.id.fragment_b_control_toggle) void toggleFragmentB(CompoundButton toggleButton, boolean checked) { Log.d(LOG_TAG, "toggle " + checked); //this will have bug when "Do no keep activities" turned on //because, when the FragmentB is shown, home exit and enter again, the system auto recover the fragment //the checked status is also recovered, this method will invoke once, with the checked is true //So, the fragmentB is added twice, and remove button can only remove one of them if (checked) { addFragmentB(); } else { removeFragmentB(); } }
添加方法和移除方法如下:
private void addFragmentB() { //You can also add/remove fragment dynamically FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentB = new FragmentB(); fragmentTransaction.add(R.id.fragment_container, fragmentB); fragmentTransaction.commit(); } private void removeFragmentB() { FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.remove(fragmentB); fragmentTransaction.commit(); }
在Do not keep activities开启的时候就出现了问题,因为返回回来系统恢复了一个FragmentB,为了恢复View的状态, toggleButton的事件又进入了一次,所以又add了一个新的FragmentB.
想了一些办法,比如:
fragmentB.isAdded()判断: 不管用,因为fragmentB是新的实例.
把FragmentB写成单例: Home退出又返回的时候就抛异常了,因为系统在自动恢复的时候无法调用它的构造方法.
后来用了tag解决了:
private void addFragmentB() { FragmentManager fragmentManager = getFragmentManager(); Fragment fragment = fragmentManager.findFragmentByTag(FragmentB.TAG); if (null == fragment) { FragmentB fragmentB = new FragmentB(); Log.i(LOG_TAG, "do add fragmentB action"); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(R.id.fragment_container, fragmentB, FragmentB.TAG); fragmentTransaction.commit(); } } private void removeFragmentB() { FragmentManager fragmentManager = getFragmentManager(); Fragment fragment = fragmentManager.findFragmentByTag(FragmentB.TAG); if (null != fragment) { FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.remove(fragment); fragmentTransaction.commit(); } }
当然这只是我的小demo的问题解决了,实际项目中要看实际情况了,虽然开始感觉比较复杂,知道原理之后就可以抽丝剥茧了.
参考资料:
博文:
Launch mode, task和back stack: http://www.cnblogs.com/mengdd/archive/2013/06/13/3134380.html
Fragment和Activity: http://www.cnblogs.com/mengdd/archive/2013/01/11/2856374.html
关于状态恢复: