一、为什么需要MVVM架构?
想象一下你正在开发一个天气预报应用。最初你可能把所有代码都写在Activity里:网络请求、数据解析、界面更新...很快你会发现这个Activity变得臃肿不堪,就像塞满衣服的行李箱,每次修改都要小心翼翼生怕扯出别的代码。
MVVM架构就像给你的代码找了个收纳师,它把代码分成三层:
- Model:负责数据(比如从服务器获取天气数据)
- View:只管界面显示(比如显示温度、天气图标)
- ViewModel:中间人,把Model的数据"翻译"成View能直接用的格式
这样做最大的好处是:当后台API变更时,你只需要修改Model层;当UI设计改版时,你只需要调整View层。两边各忙各的,互不干扰。
二、LiveData和ViewModel这对黄金搭档
2.1 ViewModel的生命周期魔法
传统开发中,屏幕旋转会导致Activity重建,所有临时数据都会丢失。ViewModel的厉害之处在于,它能在配置变更(如屏幕旋转)时存活下来。
// 技术栈:Android + Kotlin
class WeatherViewModel : ViewModel() {
// 温度数据
var temperature = 0
// 获取天气数据的方法
fun fetchData(city: String) {
// 这里实际是网络请求的模拟
temperature = if (city == "北京") 28 else 25
}
}
// 在Activity中使用
class WeatherActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val viewModel = ViewModelProvider(this).get(WeatherViewModel::class.java)
viewModel.fetchData("北京")
// 即使屏幕旋转,这里获取的还是之前的数据
Log.d("Temperature", viewModel.temperature.toString())
}
}
2.2 LiveData的自动通知机制
LiveData就像个智能喇叭,当数据变化时会自动通知所有订阅者。再也不用手动调用TextView.setText()了!
class WeatherViewModel : ViewModel() {
// 改用LiveData包装温度数据
val temperature = MutableLiveData<Int>()
fun fetchData(city: String) {
// 模拟网络请求完成后更新数据
temperature.value = if (city == "北京") 28 else 25
}
}
class WeatherActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val viewModel = ViewModelProvider(this).get(WeatherViewModel::class.java)
// 观察温度变化,自动更新UI
viewModel.temperature.observe(this) { temp ->
findViewById<TextView>(R.id.temp_text).text = "当前温度:$temp℃"
}
viewModel.fetchData("北京")
}
}
三、完整实战:构建天气应用
让我们用完整的代码示例把理论落地。这个示例包含网络请求、数据转换和UI绑定。
3.1 数据层(Model)
// 网络数据源
object WeatherRepository {
// 模拟从网络获取天气数据
fun getWeather(city: String): LiveData<Weather> {
val result = MutableLiveData<Weather>()
// 实际项目中这里应该是Retrofit网络请求
Handler(Looper.getMainLooper()).postDelayed({
result.value = when(city) {
"北京" -> Weather(28, "晴")
"上海" -> Weather(26, "多云")
else -> Weather(25, "阴")
}
}, 1000)
return result
}
}
// 数据实体类
data class Weather(
val temperature: Int,
val condition: String
)
3.2 ViewModel层
class WeatherViewModel : ViewModel() {
private val repository = WeatherRepository
// 对外暴露的LiveData
val weatherInfo = MutableLiveData<String>()
// 错误信息
val errorMessage = MutableLiveData<String>()
fun loadWeather(city: String) {
repository.getWeather(city).observeForever { weather ->
weather?.let {
// 数据格式转换
weatherInfo.value = "${city}天气:${it.condition} ${it.temperature}℃"
} ?: run {
errorMessage.value = "获取天气数据失败"
}
}
}
// 避免内存泄漏
override fun onCleared() {
super.onCleared()
// 这里应该取消所有网络请求
}
}
3.3 UI层(View)
class WeatherActivity : AppCompatActivity() {
private lateinit var viewModel: WeatherViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_weather)
viewModel = ViewModelProvider(this).get(WeatherViewModel::class.java)
// 观察天气数据变化
viewModel.weatherInfo.observe(this) { info ->
findViewById<TextView>(R.id.weather_text).text = info
}
// 观察错误信息
viewModel.errorMessage.observe(this) { error ->
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
}
// 点击按钮加载天气
findViewById<Button>(R.id.load_button).setOnClickListener {
val city = findViewById<EditText>(R.id.city_input).text.toString()
if (city.isNotEmpty()) {
viewModel.loadWeather(city)
}
}
}
}
四、MVVM的进阶技巧
4.1 数据转换神器Transformations
当原始数据需要加工时,不要在UI层做,应该使用Transformations在ViewModel中转换:
class WeatherViewModel : ViewModel() {
private val rawWeatherData = MutableLiveData<Weather>()
// 转换后的UI数据
val uiWeatherData = Transformations.map(rawWeatherData) { weather ->
"温度:${weather.temperature}℃ 状态:${weather.condition}"
}
fun loadData(city: String) {
// 获取原始数据
WeatherRepository.getWeather(city).observeForever {
rawWeatherData.value = it
}
}
}
4.2 避免内存泄漏的三条军规
- 在ViewModel中不要持有Activity/Fragment引用
- 使用viewLifecycleOwner替代Fragment的lifecycleOwner
- 记得在onCleared()中取消所有异步任务
// 正确写法(在Fragment中)
viewModel.data.observe(viewLifecycleOwner) { data ->
// 更新UI
}
五、MVVM的优缺点分析
5.1 优势明显
- 代码解耦:UI改动不影响业务逻辑,业务逻辑变化不波及UI
- 生命周期安全:LiveData自动管理订阅,不会因Activity销毁导致崩溃
- 数据持久化:ViewModel在配置变更时保留数据
- 可测试性:业务逻辑可以单独测试,不需要依赖Android环境
5.2 潜在问题
- 学习曲线:对新手来说概念较多
- 过度设计:简单页面可能显得繁琐
- 回调嵌套:多个LiveData可能导致"回调地狱"
5.3 最佳实践场景
- 中大型项目,特别是需要长期维护的
- 需要频繁迭代UI设计的项目
- 多人协作开发,需要明确分工的场景
- 需要高度可测试性的项目
六、从入门到精通的建议
- 从小功能开始尝试,比如先改造一个页面
- 善击Android Studio的Live Templates,快速生成观察代码
- 结合Data Binding使用可以进一步减少样板代码
- 当遇到复杂数据流时,考虑使用Kotlin Flow替代LiveData
- 定期回顾代码结构,防止ViewModel变得臃肿
记住,架构不是银弹。MVVM虽好,但也要根据项目实际情况灵活调整。当你的Activity超过1000行代码时,就是考虑MVVM的好时机;当你的ViewModel开始膨胀时,就该考虑进一步模块化了。
评论