视频链接:https://www.bilibili.com/video/av29949898/?spm_id_from=333.788.videocard.1
背景:为了帮助开发者更高效、更容易地构建优秀的应用,Google推出了Android Jetpack, 它包含了开发库、工具、以及最佳实践指南。其中的Lifecycle库可以有效避免内存泄漏和解决常见的Android生命周期难题。Lifecycle 库2.0版本还新添加了与Data Binding的集成,ViewModel类就属于Lifecycle库。
ViewModel是用来保存应用UI数据的类,并且它会在配置变更(Configuration Change)后继续存在。让我们来看看它的特点吧。
手机屏幕旋转是Configuration Change的一种典型场景。当旋转屏幕时,Activity会被重新创建,如果数据没有被正确的保存和恢复,就有可能丢失,从而导致莫名其妙的UI错误,甚至应用崩溃。相反的,ViewModel会在Configuration Change后继续存在。
如下的架构设计是更被推荐的:将所有的UI数据保存在ViewModel中,而不是Activity中。这样就能确保数据不会受到Configuration Change的影响。
将Activity UI 数据存储在ViewModel
Android开发时一个常见的坑是把很多变量、逻辑和数据摆在Activity或Fragment中,这样的代码比较混乱和难以维护。随着业务的发展和变更,可能会导致Activity越来越膨胀和臃肿。这种开发模式违反了「单一责任原则」。
ViewMode可以有效的划分责任,它可以用来保存Activity的所有UI数据,然后,Activity仅负责了解如何在屏幕上显示该数据和接受用户互动,但是它不会处理这些互动。如果你的应用加载和存储数据,建议创建一个Repository的存储区类,另外,应该确保ViewModel不会因为承担过多责任而变得臃肿。要避免这种情况,可以创建Presenter类,或者实现一种更成熟的架构。
image.pngclass UserProfileViewModel: ViewModel() {
val user = User(name="", company = "")
}
要创建一个ViewModel,首先需要扩展ViewModel类,然后将Activity中之前与UI相关的实例变量摆放在这个ViewModel中。
override fun onCreate(savedInstanceState: Bundle?) {
//Setup Activity
//ViewModelProviders.of()创建一个ViewModelProvider实例
val userViewModel = ViewModelProviders.of(this).get(UserProfileViewModel::class.java)
userViewModel.user.name = "小明"
}
接着,在Activity的onCreate中从ViewModel Provider的框架实用类再获取ViewModel。
注意:ViewModelProvider将获取一个Activity实例。这种机制,让你可以旋转屏幕,获取一个新的Activity实例,不过,请确保它始终与同一个ViewModel关联。对于ViewModel实例,你可以使用getter函数,从Activity直接获取UI数据。ViewModel的默认构造函数是没有任何参数的。如果想要修改,可以使用ViewModelFactory(ViewModelProvider.Factory)创建ViewModel自定义构造函数。
上面是ViewModel最简单的用例。不过,ViewModel类也可以很好地与LiveData和DataBinding互相搭配使用。使用ViewModel和LiveData,你可以创建反应式界面。也就是说,当底层数据被更新时,UI也会相应的自动更新。
image.png
image.png
来看代码:
- 我们假设你的ViewModel包含LiveData,可以像平常一样利用DataBinding来绑定数据。
class UserProfileViewModel: ViewModel() {
// _user and user are for proper encapsulation
private val _user = MutableLiveData<User>()
val user:LiveData<User>
get() = _user
}
- 在这示例XML文件中,包含你的ViewModel的数据绑定布局和变量标记
<layout>
<data>
<variable
name="viewmodel"
type="android.example.com.UserProfileViewModel"
</data>
<.../>
</layout>
- 然后,在你的Activity或Fragment中,将XML中使用的变量绑定关联
override fun onCreate(savedInstanceState: Bundle?) {
//...
val binding = ActivityMainBinding.inflate(layoutInflater)
binding.viewmodel = userVieModel
binding.setLifecycleOwner(this)//关键代码:绑定每次LiveData数据的更新
setContentView(binding.root)
}
- 再回到2.中的XML文件,这时,就可以直接从XML中的ViewModel引用LiveData字段
<layout>
<data>
<variable
name="viewmodel"
type="android.example.com.UserProfileViewModel"
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewmodel.user.name}" />
</layout>
当与Binding Adapter用在一起时,你就可以在Activity中省去大量样板代码逻辑。
注意:此功能需要Android Studio 3.1或以上版本中支持。
image.png最后,介绍一些最佳实践
注意:
- 不要将Context传入ViewModel,也就是说,Fragment、Activity和View都不能被传入。
正如我们之前看到的一样,ViewModel可以比相联的Activity和Fragment的生命周期更长。
当你旋转屏幕时,Activity将被销毁,但是,ViewModel还存储着已经被销毁的Activity的引用,这情况就是一种内存泄漏。
另外,如果你需要比ViewModel的生命周期更长的Application类,可以使用AndroidViewModel子类,透过这个子类,你就可以直接使用Application的引用了。 - ViewModel并不应取代onSaveInstanceState的使用,它们俩是相辅相成的。
- 当进程被关闭时,ViewModel将被销毁,但是onSaveInstanceState不会受到影响。
-
另外,ViewModel可以用来存储大量数据,而onSaveInstanceState只能用来储存有限的数据。
我们尽可能把多一点UI数据往ViewModel内存储,以便在Configuration Change时不需要重新加载或生成数据
image.png
image.png
-
另一方面,如果进程被Framework关闭,我们应该用onSaveInstanceState来存储足够还原UI状态的最少量数据,例如,用户的数据库 ID
image.png
网友评论