一、为什么需要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 避免内存泄漏的三条军规

  1. 在ViewModel中不要持有Activity/Fragment引用
  2. 使用viewLifecycleOwner替代Fragment的lifecycleOwner
  3. 记得在onCleared()中取消所有异步任务
// 正确写法(在Fragment中)
viewModel.data.observe(viewLifecycleOwner) { data ->
    // 更新UI
}

五、MVVM的优缺点分析

5.1 优势明显

  • 代码解耦:UI改动不影响业务逻辑,业务逻辑变化不波及UI
  • 生命周期安全:LiveData自动管理订阅,不会因Activity销毁导致崩溃
  • 数据持久化:ViewModel在配置变更时保留数据
  • 可测试性:业务逻辑可以单独测试,不需要依赖Android环境

5.2 潜在问题

  • 学习曲线:对新手来说概念较多
  • 过度设计:简单页面可能显得繁琐
  • 回调嵌套:多个LiveData可能导致"回调地狱"

5.3 最佳实践场景

  • 中大型项目,特别是需要长期维护的
  • 需要频繁迭代UI设计的项目
  • 多人协作开发,需要明确分工的场景
  • 需要高度可测试性的项目

六、从入门到精通的建议

  1. 从小功能开始尝试,比如先改造一个页面
  2. 善击Android Studio的Live Templates,快速生成观察代码
  3. 结合Data Binding使用可以进一步减少样板代码
  4. 当遇到复杂数据流时,考虑使用Kotlin Flow替代LiveData
  5. 定期回顾代码结构,防止ViewModel变得臃肿

记住,架构不是银弹。MVVM虽好,但也要根据项目实际情况灵活调整。当你的Activity超过1000行代码时,就是考虑MVVM的好时机;当你的ViewModel开始膨胀时,就该考虑进一步模块化了。