Android(安卓)Do not keep activities选项分析
16lz
2021-01-26
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.htmlDo 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的问题解决了,实际项目中要看实际情况了,虽然开始感觉比较复杂,知道原理之后就可以抽丝剥茧了.
参考资料:
文中Demo项目地址: https://github.com/mengdd/HelloActivityAndFragment 博文: Launch mode, task和back stack: http://www.cnblogs.com/mengdd/archive/2013/06/13/3134380.html 进程的生命周期: http://www.cnblogs.com/mengdd/p/3139934.html Activity生命周期: http://www.cnblogs.com/mengdd/archive/2012/12/01/2797784.html Fragment和Activity: http://www.cnblogs.com/mengdd/archive/2013/01/11/2856374.html 官网文档: http://developer.android.com/guide/components/tasks-and-back-stack.html 关于状态恢复: http://www.intertech.com/Blog/saving-and-retrieving-android-instance-state-part-1/ http://www.intertech.com/Blog/saving-and-retrieving-android-instance-state-part-2/更多相关文章
- Android(安卓)ApiDemos示例解析(11):App->Activity->Receive Resu
- Android(安卓)Binder Mechanism (4) -- 如何使用已注册的系统Ser
- Android面面观——Android事件处理下(按键、触摸屏和滚动球的一些
- C调用Java
- Android(安卓)Audio代码分析=Audio Strategy
- Android(安卓)两种启动Service(远程)的方式:Bind 与Start
- android使用html+javascript来制作页面
- debug android 系统方式
- IOS和Android的区别[转]