MVVM 概念梳理
一、MVVM 概念
MVVM(Model-View-ViewModel)是一种软件架构设计模式,最早由微软提出,特别适用于构建用户界面的应用程序。在MVVM模式中,界面被拆分为三个核心部分:Model、View 和 ViewModel。
-
Model:它仅仅关注数据本身,不关心任何行为。Model 提供了应用程序所需的数据和业务逻辑。
-
View:负责接收用户输入、发起数据请求及展示结果页面。在Android中,View 通常对应于Activity或Fragment。
-
ViewModel:它是MVVM架构中的核心部分,负责业务逻辑处理,并将Model中的数据与View进行绑定。ViewModel不持有View的引用,而是通过数据绑定机制与View进行通信。
二、MVVM 原理
MVVM 的核心原理是实现View与Model的双向绑定,使得当Model中的数据发生变化时,View能够自动更新;同样,当View中的用户输入或操作导致数据变化时,Model也能同步更新。在Android中,这一原理通常通过Jetpack提供的LiveData、ViewModel等组件来实现。
三、MVVM 常见组件
-
LiveData:一个可观察的数据持有者,它持有某个数据并允许数据绑定组件观察这个数据。当数据变化时,LiveData 会通知观察者。
-
ViewModel:存储和管理UI相关的数据,即使配置发生变化(如屏幕旋转),数据也会保持不变。ViewModel负责将Model中的数据传递给View,并处理与数据相关的业务逻辑。
-
Repository:负责数据的获取和存储,可以是网络请求、本地数据库或文件等。Repository通常作为Model的一部分,为ViewModel提供数据服务。
四、MVVM 常见用法
在Android开发中,MVVM架构模式通常用于构建复杂的应用程序,以提高代码的可维护性和可扩展性。以下是MVVM架构的常见用法:
-
数据绑定:通过LiveData和DataBinding库,实现View与ViewModel之间的数据绑定,使界面能够自动更新。
-
业务逻辑处理:将业务逻辑从View中分离出来,放入ViewModel中进行处理,以提高代码的可读性和可测试性。
-
配置变化处理:通过ViewModel保持数据在配置变化(如屏幕旋转)时的稳定性,避免重复的网络请求或数据计算。
五、Android开发实例
实例一:天气应用
在这个例子中,我们构建一个简单的天气应用,它能够从网络获取天气数据并显示在界面上。同时,用户可以通过点击按钮来更新天气信息。
-
Model:定义一个数据类来表示天气信息,并通过Repository类来获取和存储天气数据。
-
View:使用Activity或Fragment来展示天气信息,并包含一个按钮用于更新天气。
-
ViewModel:包含一个LiveData对象来持有天气数据,并提供获取和更新天气数据的方法。
代码示例(简化版):
// Model(数据类)
data class Weather(val description: String)
// Repository(数据获取和存储)
class WeatherRepository {
// 模拟网络请求获取天气数据
fun getWeather(): Weather {
// 省略网络请求代码,直接返回模拟数据
return Weather("晴天")
}
}
// ViewModel
class WeatherViewModel : ViewModel() {
val weatherResult = MutableLiveData<Weather>()
init {
// 初始化时获取天气数据
weatherResult.postValue(WeatherRepository().getWeather())
}
fun updateWeather(description: String) {
// 更新天气数据
weatherResult.postValue(Weather(description))
}
}
// View(Activity)
class WeatherActivity : AppCompatActivity() {
private lateinit var binding: ActivityWeatherBinding
private lateinit var viewModel: WeatherViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWeatherBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this).get(WeatherViewModel::class.java)
binding.btnUpdateWeather.setOnClickListener {
viewModel.updateWeather("阴天")
}
viewModel.weatherResult.observe(this, Observer {
weather ->
binding.tvWeather.text = weather.description
})
}
}
(注意:上述代码为简化版,实际开发中需要处理网络请求错误、生命周期管理等问题。)
实例二:用户列表应用
在这个例子中,我们将构建一个用户列表应用,该应用将从本地数据库(如Room数据库)中获取用户数据,并在RecyclerView中展示这些数据。我们将使用MVVM架构模式来实现这一功能。
1. Model层
首先,我们定义一个User
数据类来表示用户信息,并创建一个UserDao
接口来定义数据库操作。
// User.kt
data class User(
@PrimaryKey(autoGenerate = true) val id: Int,
val name: String,
val email: String
)
// UserDao.kt
@Dao
interface UserDao {
@Query("SELECT * FROM User")
fun getAllUsers(): LiveData<List<User>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(user: User)
// 其他数据库操作...
}
2. ViewModel层
接下来,我们创建一个UserViewModel
类来处理与用户数据相关的业务逻辑。UserViewModel
将包含一个LiveData<List<User>>
对象来持有用户列表数据,并提供一个方法来刷新用户列表。
// UserViewModel.kt
class UserViewModel : ViewModel() {
private val userRepository: UserRepository
val allUsers: LiveData<List<User>>
init {
userRepository = UserRepository(UserDatabase.getDatabase(application).userDao())
allUsers = userRepository.getAllUsers()
}
fun refreshUsers() {
// 在这里可以添加逻辑来从服务器或其他数据源刷新用户数据,
// 但在这个例子中,我们仅从本地数据库获取数据。
// 由于我们使用的是LiveData,所以当数据库中的数据变化时,UI会自动更新。
}
// 清理资源(如果需要的话)
override fun onCleared() {
super.onCleared()
// 可以在这里取消任何正在进行的网络请求或清理其他资源。
}
}
// UserRepository.kt(可选,用于封装数据库操作)
class UserRepository(private val userDao: UserDao) {
val allUsers: LiveData<List<User>> = userDao.getAllUsers()
// 其他数据库操作封装...
}
注意:在这个例子中,我们创建了一个UserRepository
类来封装数据库操作,但这并不是MVVM架构的必需部分。你可以直接在UserViewModel
中调用UserDao
的方法。
3. View层
最后,我们在Activity或Fragment中设置RecyclerView来展示用户列表,并观察UserViewModel
中的allUsers
数据。
// UserListActivity.kt
class UserListActivity : AppCompatActivity() {
private lateinit var binding: ActivityUserListBinding
private lateinit var userViewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserListBinding.inflate(layoutInflater)
setContentView(binding.root)
userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
binding.userRecyclerView.layoutManager = LinearLayoutManager(this)
binding.userRecyclerView.adapter = UserAdapter()
userViewModel.allUsers.observe(this, Observer {
users ->
// 更新RecyclerView的适配器数据
(binding.userRecyclerView.adapter as UserAdapter).submitList(users)
})
// 可以在这里添加按钮或其他UI元素来调用userViewModel.refreshUsers()方法(如果需要的话)
}
// UserAdapter.kt(RecyclerView的适配器)
class UserAdapter : ListAdapter<User, UserAdapter.UserViewHolder>(DiffCallback) {
// DiffCallback的实现用于高效地更新RecyclerView中的数据
private object DiffCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean = oldItem == newItem
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return UserViewHolder(itemView)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val user = getItem(position)
holder.bind(user)
}
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val nameTextView: TextView = itemView.findViewById(R.id.nameTextView)
private val emailTextView: TextView = itemView.findViewById(R.id.emailTextView)
fun bind(user: User) {
nameTextView.text = user.name
emailTextView.text = user.email
}
}
}
}
在这个例子中,我们创建了一个UserAdapter
类来作为RecyclerView的适配器,并使用DiffUtil
来高效地更新RecyclerView中的数据。我们还使用了Data Binding库来简化View与ViewModel之间的数据绑定。
请注意,上述代码示例是简化版的,实际开发中可能需要处理更多的细节,如错误处理、生命周期管理等。此外,为了完整性,可能还需要创建一个UserDatabase
类来配置Room数据库,并在AndroidManifest.xml
中声明必要的权限。