ViewModel 的生命周期与数据保持

6.1.1 配置变更数据保持

什么是配置变更

配置变更是指设备配置发生变化,系统会重建 Activity/Fragment:

  • 屏幕旋转
  • 语言切换
  • 深色模式切换
  • 键盘可用性变化

配置变更的问题

kotlin 复制代码
// 传统方式 - 配置变更导致数据丢失
class MainActivity : AppCompatActivity() {
    
    private var userData: User? = null
    private var orderList: List<Order>? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 从 Intent 或网络加载数据
        userData = loadUserData()
        orderList = loadOrderList()
        
        updateUI(userData, orderList)
    }
    
    // 屏幕旋转时,Activity 重建,数据丢失
    // onCreate 再次执行,需要重新加载数据
}

ViewModel 的解决方案

kotlin 复制代码
// 使用 ViewModel - 配置变更时数据保持
class MainActivity : AppCompatActivity() {
    
    private val viewModel: UserViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // ViewModel 在配置变更时保持不变
        viewModel.userData.observe(this) { user ->
            updateUI(user)
        }
        
        viewModel.orderList.observe(this) { orders ->
            updateUI(orders)
        }
    }
}

class UserViewModel : ViewModel() {
    
    private val _userData = MutableLiveData<User>()
    val userData: LiveData<User> = _userData
    
    private val _orderList = MutableLiveData<List<Order>>()
    val orderList: LiveData<List<Order>> = _orderList
    
    init {
        // 只在 ViewModel 首次创建时加载一次数据
        loadUserData()
        loadOrderList()
    }
    
    private fun loadUserData() {
        viewModelScope.launch {
            val user = userRepository.getUser()
            _userData.value = user
        }
    }
    
    private fun loadOrderList() {
        viewModelScope.launch {
            val orders = orderRepository.getOrders()
            _orderList.value = orders
        }
    }
}

6.1.2 分离视图逻辑与业务逻辑

传统方式的问题

kotlin 复制代码
// 传统方式 - 视图逻辑与业务逻辑耦合
class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 业务逻辑直接写在 Activity 中
        btnLogin.setOnClickListener {
            val username = etUsername.text.toString()
            val password = etPassword.text.toString()
            
            if (username.isEmpty()) {
                Toast.makeText(this, "用户名不能为空", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            
            if (password.isEmpty()) {
                Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            
            if (password.length < 6) {
                Toast.makeText(this, "密码至少 6 位", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            
            // 网络请求
            lifecycleScope.launch {
                try {
                    val result = login(username, password)
                    if (result.success) {
                        Toast.makeText(this@MainActivity, "登录成功", Toast.LENGTH_SHORT).show()
                        navigateToHome()
                    } else {
                        Toast.makeText(this@MainActivity, result.message, Toast.LENGTH_SHORT).show()
                    }
                } catch (e: Exception) {
                    Toast.makeText(this@MainActivity, "登录失败:${e.message}", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}

ViewModel 的解决方案

kotlin 复制代码
// 使用 ViewModel - 分离视图逻辑与业务逻辑
class MainActivity : AppCompatActivity() {
    
    private val viewModel: LoginViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 观察登录状态
        viewModel.loginState.observe(this) { state ->
            when (state) {
                is LoginState.Idle -> {
                    // 初始状态
                }
                is LoginState.Loading -> {
                    showLoading()
                }
                is LoginState.Success -> {
                    hideLoading()
                    navigateToHome()
                }
                is LoginState.Error -> {
                    hideLoading()
                    showError(state.message)
                }
            }
        }
        
        // 视图逻辑
        btnLogin.setOnClickListener {
            val username = etUsername.text.toString()
            val password = etPassword.text.toString()
            viewModel.login(username, password)
        }
    }
}

// ViewModel - 纯业务逻辑
class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {
    
    private val _loginState = MutableLiveData<LoginState>()
    val loginState: LiveData<LoginState> = _loginState
    
    fun login(username: String, password: String) {
        // 业务逻辑
        if (username.isEmpty()) {
            _loginState.value = LoginState.Error("用户名不能为空")
            return
        }
        
        if (password.isEmpty()) {
            _loginState.value = LoginState.Error("密码不能为空")
            return
        }
        
        if (password.length < 6) {
            _loginState.value = LoginState.Error("密码至少 6 位")
            return
        }
        
        // 网络请求
        viewModelScope.launch {
            _loginState.value = LoginState.Loading
            try {
                val result = loginRepository.login(username, password)
                if (result.success) {
                    _loginState.value = LoginState.Success(result.user)
                } else {
                    _loginState.value = LoginState.Error(result.message)
                }
            } catch (e: Exception) {
                _loginState.value = LoginState.Error("登录失败:${e.message}")
            }
        }
    }
}

// 登录状态
sealed class LoginState {
    object Idle : LoginState()
    object Loading : LoginState()
    data class Success(val user: User) : LoginState()
    data class Error(val message: String) : LoginState()
}

6.1.3 ViewModel 的生命周期

ViewModel 的生命周期示意图

复制代码
Activity 生命周期:
onCreate → ViewModel 创建
onStart
onResume
onPause
onStop
onDestroy (配置变更) → ViewModel 保持
onCreate → ViewModel 复用
onDestroy (真正销毁) → ViewModel 销毁

Fragment 生命周期:
onAttach → ViewModel 创建
onCreate
onCreateView
onStart
onResume
onPause
onStop
onDestroyView → ViewModel 保持
onDestroy (配置变更) → ViewModel 保持
onDetach → ViewModel 销毁

ViewModel 生命周期的特点

特性 说明
创建时机 Activity/Fragment 首次创建时
销毁时机 Activity/Fragment 真正销毁时(非配置变更)
配置变更 配置变更时 ViewModel 保持不变
范围 Activity/Fragment 的生命周期范围

相关推荐
千里马学框架14 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台14 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone14 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc15 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo17 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770017 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work18 小时前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼18 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work18 小时前
ch21 签名、校验与发版
kotlin
AFinalStone19 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui