本文译自「Why Your App Keeps Forgetting Everything」,原文链接https://medium.com/mobile-app-development-publication/why-your-app-keeps-forgetting-everything-aa9ad8dd8f6b ,由Android Dev Nexus发布于2025年6月13日。
不知你有没有发现了一个让许多 Android 开发者困惑的关键问题:ViewModel 和 savedInstanceState 解决的是不同的问题,并且拥有不同的生命周期。
让我来解释一下你的测试中究竟发生了什么,以及为什么这两种机制都存在。
Android 中的两种“死亡”类型
Android 应用可以通过两种截然不同的方式“死亡”:
1. 配置变更(屏幕旋转等)
Activity/Fragment:死亡并重建
ViewModel:幸存!🎉
savedInstanceState:也幸存,但此时你并不需要它
2. 进程死亡(应用最小化、内存不足等)
Activity/Fragment:死亡
ViewModel:也死亡!💀
savedInstanceState:幸存并成为你的生命线
测试结果不撒谎
1
2
3
4
5
6
7
8
9
10
11
// 进程死亡时会发生什么:
// 最小化应用程序之前:
class MyViewModel : ViewModel () {
var userData = "Important data"
var counter = 42
}
// 重新打开应用程序后:
// - 新的 ViewModel 实例已创建(旧数据已消失)
// - 但是 onCreate() 接收了已保存数据的 savedInstanceState 包
// - 你需要手动从 savedInstanceState 恢复 ViewModel
你猜测的完全正确:ViewModel 需要额外的步骤来存储/恢复数据,即使进程终止。
完整的解决方案:两者结合
以下是现代 Android 开发处理这个双层系统的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class MainActivity : AppCompatActivity () {
private lateinit var viewModel : MyViewModel
override fun onCreate ( savedInstanceState : Bundle ?) {
super . onCreate ( savedInstanceState )
viewModel = ViewModelProvider ( this )[ MyViewModel :: class . java ]
// 检查我们是否正在从进程死亡中恢复
if ( savedInstanceState != null && viewModel . isEmpty ()) {
// 从 savedInstanceState 恢复 ViewModel
val userData = savedInstanceState . getString ( "user_data" , "" )
val counter = savedInstanceState . getInt ( "counter" , 0 )
viewModel . restoreFromSavedState ( userData , counter )
}
}
override fun onSaveInstanceState ( outState : Bundle ) {
super . onSaveInstanceState ( outState )
// 保存关键的 ViewModel 数据以避免进程死亡
outState . putString ( "user_data" , viewModel . userData )
outState . putInt ( "counter" , viewModel . counter )
}
}
class MyViewModel : ViewModel () {
var userData : String = ""
var counter : Int = 0
fun isEmpty (): Boolean = userData . isEmpty () && counter == 0
fun restoreFromSavedState ( userData : String , counter : Int ) {
this . userData = userData
this . counter = counter
}
}
当每个机制生效时
让我来向你展示一下不同场景下的具体情况:
场景 1:屏幕旋转
1
2
3
4
5
6
7
8
Before rotation:
- ViewModel: Alive with data ✅
- savedInstanceState: Gets saved but not really needed
After rotation:
- ViewModel: Same instance, data intact ✅
- savedInstanceState: Available but redundant
- Result: ViewModel data is immediately available
场景2:应用程序最小化→重新打开(进程死亡)
1
2
3
4
5
6
7
Before minimizing:
- ViewModel: Alive with data ✅
- onSaveInstanceState() : Saves critical data to Bundle
After reopening:
- ViewModel: NEW instance, no data ❌
- savedInstanceState: Contains saved data ✅
- Result: Must restore ViewModel from savedInstanceState
场景 3:应用程序最小化 → 重新打开(进程存活)
1
2
3
4
5
6
Before minimizing:
- ViewModel: Alive with data ✅
After reopening:
- ViewModel: Same instance, data intact ✅
- savedInstanceState: null ( no recreation happened)
- Result: ViewModel data is immediately available
那么,为什么存在这个双层系统?
ViewModel 处理常见情况:
复杂的 UI 状态,你不希望在频繁旋转屏幕时丢失。
任何需要耗费大量资源重新创建的内容。
savedInstanceState 处理特殊情况:
进程死亡难以预测。但当它发生时,用户希望其状态能够持久保存。
小而关键的数据片段(例如用户输入、滚动位置)。
简单的 UI 状态,对用户体验至关重要。
Bundle 大小的现实检验
以下是一些可以帮你免去调试麻烦的事情:Bundle 并非无限大的存储空间。你大约有 1MB 的空间可用,超过这个限制会导致
崩溃,让你质疑自己的人生选择。
保持 onSavedInstanceState 数据精简。保存用户写到一半的电子邮件草稿,而不是整个联系人列表。
现代方法:SavedStateHandle
Google 意识到这种手动操作非常繁琐,因此他们创建了 SavedStateHandle :
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyViewModel ( private val savedStateHandle : SavedStateHandle ) : ViewModel () {
// 这将自动保留配置更改和进程终止!
var userData : String
get () = savedStateHandle . get < String >( "user_data" ) ?: ""
set ( value ) = savedStateHandle . set ( "user_data" , value )
var counter : Int
get () = savedStateHandle . get < Int >( "counter" ) ?: 0
set ( value ) = savedStateHandle . set ( "counter" , value )
// 无需手动恢复!
}
对于以下情况,为什么仍然需要手动 onSaveInstanceState?
1. 不属于 ViewModel 的 UI 特定状态
1
2
3
4
5
6
7
override fun onSaveInstanceState ( outState : Bundle ) {
super . onSaveInstanceState ( outState )
// 这些不属于你的业务逻辑层
outState . putInt ( "scroll_position" , recyclerView . computeVerticalScrollOffset ())
outState . putBoolean ( "is_toolbar_expanded" , collapsingToolbar . isExpanded )
outState . putParcelable ( "dialog_state" , currentDialog ?. onSaveInstanceState ())
}
2. Fragment 参数和 Activity Extras
1
2
3
4
5
6
7
8
9
10
// 这些需要在进程终止后继续存在,但不是 ViewModel 状态
class DetailFragment : Fragment () {
companion object {
fun newInstance ( itemId : String ) = DetailFragment (). apply {
arguments = Bundle (). apply { // 这在内部使用 savedInstanceState
putString ( "item_id" , itemId )
}
}
}
}
3. 视图状态过于特定于 UI
1
2
3
4
5
6
// 诸如 EditText 光标位置、焦点状态等。
override fun onSaveInstanceState ( outState : Bundle ) {
super . onSaveInstanceState ( outState )
outState . putInt ( "edit_text_selection_start" , editText . selectionStart )
outState . putInt ( "edit_text_selection_end" , editText . selectionEnd )
}
把手弄脏(动手试一试)
你可以强制终止进程进行测试:
1
2
3
# Kill your app process
adb shell am kill com.yourpackage.name
# Or use "Don't keep activities" in Developer Options
这将帮助你验证状态恢复是否正确进行。
关键洞察
ViewModel 非常适合配置更改,但需要帮助应对进程死亡。
现代方法是在 ViewModel 中使用 SavedStateHandle,它可以自动处理这两种情况。如果你尚未使用它,则需要手动执行 savedInstanceState → ViewModel 的恢复过程。
这个双层系统看似复杂,但实际上非常优雅:常见情况(配置更改)的快速恢复,以及罕见情况(进程死亡)的可靠恢复。
祝你编码愉快,愿你的状态始终持久!🚀