ViewModel 销毁时机详解

一、ViewModel 概述

ViewModel 是 Android Architecture Components 中的一个核心类,它的设计目的是存储和管理与 UI 相关的数据,并且在配置变更(如屏幕旋转)时保持数据的存活。ViewModel 的生命周期比 Activity 或 Fragment 更长,这使得它成为存储 UI 状态的理想选择。

ViewModel 的基本用法

以下是一个简单的 ViewModel 示例:

kotlin 复制代码
class MyViewModel : ViewModel() {
    private val _userData = MutableLiveData<User>()
    val userData: LiveData<User> = _userData

    init {
        loadUserData()
    }

    private fun loadUserData() {
        // 模拟从网络或数据库加载数据
        _userData.value = User("John Doe", 30)
    }
}

在 Activity 或 Fragment 中使用 ViewModel:

kotlin 复制代码
class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        viewModel.userData.observe(this) { user ->
            // 更新 UI
        }
    }
}

二、ViewModel 的生命周期

ViewModel 的生命周期与 Activity 或 Fragment 的生命周期不同。当 Activity 或 Fragment 被销毁时,ViewModel 不会立即被销毁,而是会继续存在,直到 Activity 或 Fragment 的生命周期完全结束。

生命周期对比图

事件 Activity/Fragment 状态 ViewModel 状态
Activity/Fragment 创建 已创建 已创建
配置变更(如旋转屏幕) 销毁并重建 保持不变
Activity/Fragment 完全销毁 已销毁 已销毁

三、ViewModel 的销毁时机

1. Activity 的销毁时机

对于 Activity,ViewModel 会在 Activity 完成其生命周期并被系统彻底销毁时被销毁。这通常发生在用户按下返回按钮或调用 finish() 方法时。

示例代码:

kotlin 复制代码
class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        // 使用 ViewModel
    }

    override fun onDestroy() {
        super.onDestroy()
        // 检查 Activity 是否正在被销毁(而不是因为配置变更)
        if (!isChangingConfigurations) {
            // Activity 正在被彻底销毁
            // ViewModel 将很快被销毁
        }
    }
}

2. Fragment 的销毁时机

对于 Fragment,ViewModel 的销毁时机略有不同。当 Fragment 从 Activity 中移除时,如果 Activity 仍然存在(例如,Fragment 被替换但 Activity 未被销毁),ViewModel 不会被销毁。只有当 Activity 本身被销毁时,Fragment 的 ViewModel 才会被销毁。

示例代码:

kotlin 复制代码
class MyFragment : Fragment() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 初始化视图
        return inflater.inflate(R.layout.fragment_my, container, false)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        // 视图被销毁,但 ViewModel 仍然存在
        // 除非 Fragment 所属的 Activity 也被销毁
    }
}

3. 配置变更时的行为

当发生配置变更(如屏幕旋转)时,Activity 和 Fragment 会被销毁并重新创建,但 ViewModel 会保持不变。这是 ViewModel 的一个重要特性,它允许数据在配置变更期间保持存活。

示例代码:

kotlin 复制代码
class MyViewModel : ViewModel() {
    private val _counter = MutableLiveData<Int>()
    val counter: LiveData<Int> = _counter

    init {
        _counter.value = 0
    }

    fun incrementCounter() {
        _counter.value = (_counter.value ?: 0) + 1
    }
}

class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        viewModel.counter.observe(this) { count ->
            // 更新 UI,即使在旋转屏幕后也会保持正确的值
        }
    }
}

4. 手动清除 ViewModel

在某些特殊情况下,你可能需要手动清除 ViewModel。虽然这种情况很少见,但可以通过 ViewModelStore 来实现:

kotlin 复制代码
// 手动清除 ViewModel
viewModelStore.clear()

但请注意,这通常不是必需的,因为系统会在适当的时候自动处理 ViewModel 的生命周期。

四、ViewModel 销毁时的资源释放

当 ViewModel 被销毁时,如果你有需要释放的资源(如网络连接、文件句柄等),可以重写 onCleared() 方法:

kotlin 复制代码
class MyViewModel : ViewModel() {
    private val job = Job()

    override fun onCleared() {
        super.onCleared()
        // 释放资源
        job.cancel()
    }
}

五、常见问题与最佳实践

1. 内存泄漏问题

由于 ViewModel 的生命周期比 Activity 和 Fragment 长,因此在 ViewModel 中持有 Activity 或 Fragment 的引用可能会导致内存泄漏。

错误示例:

kotlin 复制代码
class MyViewModel : ViewModel() {
    private var activity: Activity? = null // 错误!可能导致内存泄漏

    fun setActivity(activity: Activity) {
        this.activity = activity // 不要这样做
    }
}

正确做法:

kotlin 复制代码
class MyViewModel : ViewModel() {
    // 使用 Application 上下文(如果需要上下文)
    private lateinit var application: Application

    constructor(application: Application) : super() {
        this.application = application
    }
}

2. 最佳实践总结

  • 不要在 ViewModel 中持有 Activity、Fragment 或 View 的引用
  • 使用 onCleared() 方法释放资源
  • 优先使用 LiveData 或 StateFlow 来管理 UI 状态
  • 在 Activity 的 onDestroy() 或 Fragment 的 onDestroyView() 中避免访问 ViewModel,因为此时 ViewModel 可能已经被销毁

六、总结

ViewModel 的销毁时机与 Activity 和 Fragment 的生命周期密切相关,但又有所不同。理解这些差异对于正确使用 ViewModel 至关重要。通过合理管理 ViewModel 的生命周期,你可以确保数据在配置变更期间保持存活,同时避免内存泄漏和资源浪费。

相关推荐
uhakadotcom21 小时前
DuckDB相比于ClickHouse有什么不同点和优势?
后端·面试·github
2501_9159184121 小时前
iOS 开发全流程实战 基于 uni-app 的 iOS 应用开发、打包、测试与上架流程详解
android·ios·小程序·https·uni-app·iphone·webview
lichong95121 小时前
【混合开发】vue+Android、iPhone、鸿蒙、win、macOS、Linux之dist打包发布在Android工程asserts里
android·vue.js·iphone
Android出海1 天前
Android 15重磅升级:16KB内存页机制详解与适配指南
android·人工智能·新媒体运营·产品运营·内容运营
一只修仙的猿1 天前
毕业三年后,我离职了
android·面试
编程乐学1 天前
安卓非原创--基于Android Studio 实现的新闻App
android·ide·android studio·移动端开发·安卓大作业·新闻app
加载中3611 天前
pnpm时代包版本不一致问题还是否存在
前端·面试·npm
雅雅姐1 天前
Android14 init.rc中on boot阶段操作4
android
学历真的很重要1 天前
Claude Code Windows 原生版安装指南
人工智能·windows·后端·语言模型·面试·go
yinke小琪1 天前
消息队列如何保证消息顺序性?从原理到代码手把手教你
java·后端·面试