1.官方文档
MVVM 官方文档: https://developer.android.com/jetpack/docs/guide
ViewModel 文档: https://developer.android.com/topic/libraries/architecture/viewmodel
ViewModel保存状态: https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate
LiveData 官方文档: https://developer.android.com/topic/libraries/architecture/livedata
2.android mvvm 软件架构简介
2.1 架构图示
其中数字的简单描述如下:
- View通过ViewModel获取数据对象展示,并成为它的观察者。
- ViewModel通过Model得到真正的数据对象,根据业务需求通过model修改数据
- Model 只负责数据的操作:查找、从服务器下载新数据、修改等操作。
2.2 View
- 负责数据显示、用户交互,系统交互。android系统中的Fragment 、Activity。
- 只通过ViewModel得到目标数据对象展示并成为它的观察观察者。数据对象存在LiveData<XX>中。
- 不处理业务逻辑,不打开数据库,不通过网络请求数据等.
2.3 Model
- 数据抽象,封装
- 数据存储(文件、内存、服务器、本地数据库等)、数据提取
- 为ViewModel返回真正的数据对象(LiveData<xxx>)
2.4 ViewModel
- 负责为View(View(Fragment 或 Activity))准备数据
- 处理业务逻辑,不直接处理数据。
- 与模型进行通信,后者存储、网络请求数据等
2.5 LiveData
- 负责关联view与viewModel
3.LiveData
- LiveData 一系列类模板,负责关联view与viewModel,采用观察者模式、类里存放被观察的数据,它的子类如下。
- 具有系统组件(activity,fragment,service)生命周期感知能力(无需手动),系统确保只会将更新通知给活跃的观察者。
- 不支持非主线程更新
1 2020-07-22 11:18:54.688 22609-22704/com.example.frgmtvm E/AndroidRuntime: FATAL EXCEPTION: Timer-0 2 Process: com.example.frgmtvm, PID: 22609 3 java.lang.IllegalStateException: Cannot invoke setValue on a background thread 4 at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:462) 5 at androidx.lifecycle.LiveData.setValue(LiveData.java:304) 6 at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50) 7 at com.example.frgmtvm.ui.main.MainViewModel$test1$1.run(MainViewModel.kt:28) 8 at java.util.TimerThread.mainLoop(Timer.java:562) 9 at java.util.TimerThread.run(Timer.java:512)
4.ViewModel
- 如果在Activity、Fragment中保存数据,容易引起3个问题:
- 当它们被销毁后再次重新构建的时候,需要重新请求加载数据,造成资源的浪费,
- 如果请求数据是异步的,在销毁Activity时,容易引起内存泄漏。
- Activity、Fragmen代码膨胀、不易维护、测试。
- android中 ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。让数据可在发生屏幕旋转等配置更改后继续留存在.
- 绝不能引用视图、
Lifecycle
或可能存储对 Activity 上下文的引用的任何类 ViewModel
对象应该比它们相应View
对象存在的时间更长,因此ViewModel
实现中不得包含对View
对象的直接引用。- 如果 ViewModel 需要 Application 上下文(例如,为了查找系统服务),可以扩展 AndroidViewModel 类
- ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 ViewModelStoreOwner。ViewModel 将一直留在内存中,直到限定其存在时间范围的 ViewModelStoreOwner 永久消失:对于 Activity,是在 Activity finish时;而对于 Fragment,是在 Fragment detach时。
- SavedStateHandle 用来保存轻量的页面状态(key-value),可以把它们传给VIewModel 使用。
5.示例代码
完整代码:
5.1 第1步:定义Model
- User
1 class User { 2 var name = "TEST-" 3 var age = 0 4 5 override fun toString(): String { 6 return "$name-$age" 7 } 8 }
-
UserModel, 数据对象用LiveData系列类保存
1 class UserModel { 2 3 var userData = MediatorLiveData<User>() 4 5 init { 6 val user = User() 7 userData.value = user 8 } 9 10 fun userFromSql() : LiveData<User> { 11 return userData 12 } 13 fun userFromServer() : LiveData<User>{ 14 return userData 15 } 16 fun userFromFile() : LiveData<User>{ 17 return userData 18 } 19 fun userFromCache() : LiveData<User>{ 20 return userData 21 } 22 23 fun modifyUser(age : Int,name : String){ 24 val user = userData.value 25 user?.age = age 26 user?.name = name 27 28 userData.value = user 29 } 30 31 32 }
5.2 第2步:定义ViewModel
定义ViewModel,保存model对象,并提供返回数据对象的方法
1 class MainViewModel : ViewModel{ 2 3 var userModel : UserModel = UserModel() 4 5 fun loadUser() : LiveData<User>{ 6 val ud = userModel.userFromServer() 7 return ud 8 } 9 override fun onCleared() { 10 super.onCleared() 11 Log.e("MainViewModel","onCleared") 12 cancelTask() 13 } 14 //... 15 }
5.3 第3步:关联View与ViewModel
在android中,Fragment、activity被当作View
1 class Fragment1 : Fragment() { 2 3 lateinit var nameKey : TextView 4 lateinit var nameValue : EditText 5 lateinit var ageKey : TextView 6 lateinit var ageValue : TextView 7 8 val viewModel : MainViewModel by lazy { initViewModel() } 9 10 fun initViewModel() : MainViewModel{ 11 // return ViewModelProvider(this).get(MainViewModel::class.java) 12 return ViewModelProvider(activity!!).get(MainViewModel::class.java) 13 } 14 15 fun bindViewModel(){ 16 viewModel.loadUser().observe(viewLifecycleOwner){user-> 17 nameValue.setText(user.name) 18 ageValue.setText(user.age.toString()) 19 } 20 } 21 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 22 savedInstanceState: Bundle?): View { 23 val v = inflater.inflate(R.layout.view1, container, false) 24 initView(v) 25 return v 26 } 27 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 28 super.onViewCreated(view, savedInstanceState) 29 bindViewModel() 30 } 31 //... 32 }
其中:
- 第11行,绑定view与viewModel时使用的ViewModelStoreOwner是Fragment,viewModel的生命期与Fragment关联,退出Fragment后,数据失效
- 第12行,使用ViewModelStoreOwner 是 Activity,该viewModel的生命期长,退出Activity才失效,可用于多个fragment 共享数据。
- 第16行,关联view与对应的数据,当数据变化时,这里会得到通知,然后刷新界面。