Activity/Fragment 状态缓存和恢复的最佳实践

Activity/Fragment 状态缓存和恢复的最佳实践

发表于2016/8/3 15:35:32  141人阅读

分类: 翻译

The Real Best Practices to Save/Restore Activity’s and Fragment’s state 
英文原文:https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en

几个月前我发布过一篇关于Fragment缓存和恢复状态的文章:Probably be the best way (?) to save/restore Android Fragment’s state so far . 收到了许多来自世界各地Android开发者的宝贵建议和反馈,在此对大家说声谢谢嗷☺。(这里的文章原文博主已经删除,国内有翻译,下文中的StatedFragment就是这篇文章中的最终产物,为的是更方便的实现Fragment状态的缓存和恢复,不过已经不推荐大家使用)。 
但是StatedFragment的策略和官方设定的缓存策略是不一样的,因为官方设定的策略可能让开发者能更容易的理解Fragment的状态缓存和恢复,使其表现的像Activity那样能同时操控View和变量。为此我做了个实验来测试StatedFragment,以验证它是否更加容易理解?它的设计对开发者来说是否更加友好?经过两个月的测试,我想我已经得出结论了:虽然StatedFragment显得更容易理解,但同时它也带来了一个大问题,即它破坏了Android View的架构设计,可能在以后带来更多问题,并且,我也开始怀疑之前写的代码有点诡异… 因为这些原因,我决定从现在开始废弃掉StatedFragment。当然,为了拟补这个错误,我决定写下这篇博客来展示下Android中Fragment状态缓存和恢复的最佳实践。

理解当Activity状态被缓存或回收时发生了什么 
当Activity的onSaveInstanceState 方法被调用时,Activity将自动搜集它所有子View的状态,但是要注意,只有内部实现了状态缓存和恢复接口的View才能被搜集到!(即Activity的子View必须在内部实现状态缓存和恢复的接口) ,随后在onRestoreInstanceState方法被调用的时候,Activity将根据View的id(android:id)来把缓存的数据一对一的传递回去,如下图所示。 
Activity/Fragment 状态缓存和恢复的最佳实践_第1张图片 
Activity/Fragment 状态缓存和恢复的最佳实践_第2张图片 
正是因为这个原因,EditText才能在Activity被销毁后重建到时候仍然保持着销毁前的状态,即使我们啥也没干。这不是魔法,这些View能自动的缓存和恢复状态。这也是为什么哪些没有id(android:id)的View就不能缓存和恢复状态的原因。 
虽然这些View能自动的缓存状态,但是对于Activity的成员变量来说就行不通了,成员变量会随着Activity的销毁而被回收,所以你必须通过onSaveInstanceState方法和onRestoreInstanceState方法手动地缓存和恢复它们。示例如下: 
Activity/Fragment 状态缓存和恢复的最佳实践_第3张图片

理解当Fragment状态被缓存或回收时发生了什么 
当Fragment被系统销毁时,它的表现和Activity是一样一样的。如下图所示: 
Activity/Fragment 状态缓存和恢复的最佳实践_第4张图片 
Activity/Fragment 状态缓存和恢复的最佳实践_第5张图片 
这就意味着每一个成员变量也会被销毁掉,所以你必须通过onSaveInstanceState方法和onActivityCreated方法来手动地缓存和恢复状态。同志们要注意啊,Fragment里是没有onRestoreInstanceState方法的,这里和Activity不一样哟~示例如下: 
Activity/Fragment 状态缓存和恢复的最佳实践_第6张图片

对于Fragment来说有些特殊情况是和Activity不一样的,我希望大家能注意下:当Fragment从栈中重新显示的时候,他的View将会被销毁重建! 
Activity/Fragment 状态缓存和恢复的最佳实践_第7张图片 
这种情况下要知道,Fragment对象本身并没有被销毁,只是它包含的View会销毁重建。 这种情况下是不会触发缓存状态的。那么当这些重建的View会有什么样的表现呢?其实这不是啥问题,Android早就考虑过这个。这种情况下View内部的状态缓存和恢复接口会被触发,所以只要是实现了这些接口的View都会自动缓存和恢复它自己的状态,例如具有android:freezeText=”true”属性的EditText或 TextView,这样它就和销毁前的状态一样了。如下图所示: 
Activity/Fragment 状态缓存和恢复的最佳实践_第8张图片 
再次提醒下,这种情况下只有View会销毁重建,Fragment对象本身一直没变,所以它内部的成员变量也不会被销毁,所以你不需要做额外工作。 
Activity/Fragment 状态缓存和恢复的最佳实践_第9张图片

呐,现在你应该明白了,只要是实现了状态缓存和恢复接口的View,在Fragment内部都能自动的进行状态缓存和恢复,而不需要添加额外的工作,那么重点来了,对于Fragment来说,缓存和恢复状态最好的办法就是~~~

应用中所有View都在其内部实现状态缓存和恢复的接口 
Android已经提供了给View实现状态缓存和恢复的方法:onSaveInstanceState和 onRestoreInstanceState,这是开发者要去实现的任务。 
Activity/Fragment 状态缓存和恢复的最佳实践_第10张图片 
总的来说,每一个系统控件,例如EditText, TextView, Checkbox等,它们都已经在内部实现了状态缓存和恢复的接口,在使用这些特性的时候只需要设置一下相关的属性为true即可,例如TextView 设置android:freezeText=”true”。 
但是,网上众多第三方控件呢,老实讲,很多都没有在内部实现这些接口,导致在实际使用过程中可能带来大麻烦。 
如果你决定要使用第三方控件,那么你必须要确认它有在内部实现缓存和恢复状态的接口,否则你需要继承它去写一个子控件来手动实现 onSaveInstanceState/onRestoreInstanceState .示例如下 
Activity/Fragment 状态缓存和恢复的最佳实践_第11张图片 
此外,别忘了当你直接继承自View或ViewGroup的时候也要去实现这两个方法,这真的真的真的很重要哟~ 
还有!别忘了给那些你希望能自动缓存和恢复状态的View添加id!!! 
Activity/Fragment 状态缓存和恢复的最佳实践_第12张图片 
到此,我们已经搞定一半的工作了!!!

明确的区分开Fragment的状态和View的状态 
为了使你的代码更简洁和更具维护性,你必须区分开Fragment的状态和View的状态。如果一个属性是属于View的,那么在View内部去缓存和恢复它,如果一个属性是属于Fragment的,那么在Fragment内部是缓存和恢复它。(罗里吧嗦的,反正就是不要混淆了View和Fragment的状态嘛) ,示例如下: 
Activity/Fragment 状态缓存和恢复的最佳实践_第13张图片 
本帅再次提醒:不要在Fragment的onSaveInstanceState方法里缓存View的状态,反之亦然。

好了,这就是Activity/Fragment 状态缓存和恢复的最佳实践,希望能对你有帮助。


Activity,Fragment中onSaveInstanceState(Bundle outState)的调用时机


Activity 中 onSaveInstanceState(Bundle outState) 调用的时机(activity可能被销毁时调用此方法来保存瞬态数据) 
1. home键最小化时,在onPause后调用 
2. 长按home键,在onPause后调用 
3. 屏幕旋转时,在onPause后调用 
4. 开启新的Activity,在onPause后调用 
5. 补充:Android UI框架中几乎所有的UI控件都实现了onSaveInstanceState()方法,因此当activity被摧毁和重建时, 这些UI控件会自动保存和恢复状态数据,前提是你已经为这个控件指定过ID

Frament 中 onSaveInstanceState(Bundle outState) 调用的时机 
6. 列表内容 home键最小化时,在onPause后调用 长 
7. 按home键,在onPause后调用 按下电源键,在onPause后调用 
8. 托管该Fragment的Activity 旋转时,在onPause后调用 
9. 托管该Fragment的Activity开启新的Activity时,在onPause后调用 补充:即使该Fragment在回退栈中,当前展示的不是它,上面几种情况也是成立的
10. 补充:此方法被调用时,如果向outState里添加了key-value对,那么在和onCreate(Bundle savedInstanceState)和onViewCreated(View view, @Nullable Bundle savedInstanceState)中拿到的bundle中会有存入的key-value对

Activity 中 onRestoreInstanceState(Bundle savedInstanceState) 调用的时机(activity“确实”被销毁后重建,调用此方法,Fragment中无此方法) 
11. 验证的时候,只有屏幕旋转后调用了此方法,在onStart()之后 
12. onRestoreInstanceState()被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用。上面验证实验的其它4种情况Activity一般不会那么快被销毁,所以没有调用此方法 
13. 关于屏幕旋转保存数据的补充:

  1. Object onRetainCustomNonConfigurationInstance()旋转时会调用此方法,在onStop()后调用,可以重写此方法返回一个对象(Fragment中无此方法)
  2. Activity重建后可以在onCreate方法中通过getLastCustomNonConfigurationInstance()拿到上边保存的对象(Fragment中无此方法)
  3. AndroidManifest.xml中设置android:configChanges=”orientation|screenSize”后,旋转屏幕将不会重新调用各个生命周期,当然也不会调用保存数据的方法,只会调用onConfigurationChanged(Configuration newConfig)方法

Fragment 中 setRetainInstance方法介绍 
17. Fragment还可以通过setRetainInstance(boolean)来保存自定义的对象数据(Fragment中都有此方法,Activity中无此方法), 当在Fragment的onCreate()方法中调用了setRetainInstance(true)后,Activity被系统销毁又重新创建时(如屏幕旋转),可以不完全销毁Fragment,Fragment中的成员变量值会保留,恢复时跳过了onDestroy()和onCreate()


你可能感兴趣的:(Android开发)