目前可能很多人已经使用了jetpack
,目前,我已经完全将kotlin
+jetpack
用于项目中。项目开发完毕,现在回头看看一些技术细节。
onSaveInstanceState()
onSaveInstanceState()
在UI
组件停止运行、且未被主动销毁时调用。如用户点击Home
键跳转到桌面、用户跳转到新的UI
组件,该UI
组件停止运行。
若是主动销毁,onSaveInstanceState()
不会被调用。如用户主动点击返回按钮、代码调用finish()
相关的函数。
系统可能因内存过低、电量过低时回收在后台运行的UI
组件(Activity/Fragment
),用户重新打开该UI
组件时,会恢复在onSaveInstanceState()
保存的数据。
onSaveInstanceState()
适合保存少量数据。具体能保存多少数据,我并没有在任何地方看见官方说的具体值,文章Saving UI state with ViewModel SavedState and Dagger
的作者Nimrod Dayan介绍说限制在1MB
以下,尽可能是能够恢复界面数据的最小数据集。
ViewModel
ViewModel
能够在配置更改时保留,如当屏幕旋转时,UI
组件重新构建,重新得到的仍然是同一个ViewModel
。ViewModel为何能在UI
组件销毁时存活,可以看文章每日一问 Activity 都重建了,你 Fragment凭什么活着?
ViewModel
可以保存更多的数据在内存中,这些数据通常是需要在UI
上显示的。在配置更改时,UI
组件只是临时地销毁,系统会立刻生成新的UI
组件,之前存在的ViewModel
会立即通知新的UI
组件更新界面,这样界面看起来就和配置更改前完全一样了。
总结
虽然在配置更改时,ViewModel
并不会重新创建,但是在系统回收UI
组件时,ViewModel
同样会被销毁,此时唯一能保存数据的只有onSaveInstanceState()
(当然还可以持久化数据到文件/网络等)。
可以看出ViewModel
和onSaveInstanceState()
的功能都是保存数据,但是他们在某种层面上是完全不同的:
-
onSaveInstanceState()
适合保存能够恢复UI
组件的最小数据集;ViewModel
保存UI
组件显示所需的所有数据和一些额外的数据。 -
ViewModel
能够在配置更改时保留数据;onSaveInstanceState()
能够在用户临时离开UI
组件时保留数据。若需要用户在长时间离开UI
组件时保存数据,请使用数据持久化。 -
ViewModel
和onSaveInstanceState()
并不冲突,反而应该结合使用。onSaveInstanceState()
保存了恢复UI
组件的最小数据,ViewModel
需要这些数据重新恢复UI
组件需要的其他数据。
显然,ViewModel
在保存数据方面,解决了onConfigurationChanged()
的问题,并没有解决onSaveInstanceState()
的问题。
ViewModel和onSaveInstanceState()结合使用
在没有ViewModel
之前,我们会将加载数据的逻辑放在UI
组件中。UI
组件启动需要的最少数据通常会保留在启动时传入的数据集中,如Activity
启动时会在Intent
中存储对应的数据。
若下次启动该Activity
需要的最少数据没有任何修改,那么我们可以重复利用Intent
,因为系统回收UI
组件后重启该组件,系统会保留同样的Intent
。此时我们就可以不使用onSaveInstanceState()
。
但是,若在UI
组件存活时,更改过该组件启动需要的最小数据集,那么就不能使用Intent
了。我们就应该在onSaveInstanceState()
中保存最新的数据。
现代的组件架构会将业务逻辑放在ViewModel
中,所以重新启动UI
组件的最小数据集需要被传递给ViewModel
。ViewModel
依赖这些数据来加载恢复UI
组件的更多数据。此时,我们就需要结合onSaveInstanceState()
和ViewModel
使用了。
流程:
-
Activity
从Intent
获取数据,或从onCreate(Bundle)
中得到onSaveInstanceState()
保存的数据 - 将这些数据赋值给
ViewModel
-
ViewModel
使用这些数据加载UI
组件显示的数据
上面的流程虽然没有任何问题,但是ViewModel
的行为是完全依赖Intent
或onSaveInstanceState()
的数据,它并没有将ViewModel
和这些数据进行强绑定。
我们期望在构造ViewModel
时就将这些数据传递给ViewModel
使用。此时我们应该使用ViewModelProvider.Factory
。
如我们进入一个文章详情页面:
class ArticleDetailViewModel(private val id: Long) : ViewModel() {
class Factory(private val id: Long) : ViewModelProvider.Factory() {
override fun create(modelClass: Class<T>): T {
return modelClass.getConstructor(Long::class.java).newInstance(id)
}
}
private val _article = MutableLiveData<Article>()
val article: LiveData<Article>
get() = _article
// ...
}
class ArticleDetailActivity: AppCompatActivity() {
private val articleDetailViewModel: ArticleDetailViewModel by viewModels(factoryProducer = {
ArticleDetailViewModel.Factory(intent.getLong("articleId", -1L))
}
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_article_detail)
articleDetailViewModel.article.observe(this) { article ->
// show article content
}
// ...
}
}
这个文章详情页面依赖的最小数据集只有一个文章id
。通过文章id
,ViewModel
加载文章的数据,UI
组件观察到数据后进行显示。