viewModelScope 是 Android Jetpack 中 ViewModel 的一个扩展属性,为 ViewModel 提供了一个生命周期感知的协程作用域,确保协程在 ViewModel 被销毁时自动取消,避免内存泄漏。
基本使用
1. 添加依赖
// build.gradle.kts (Module)
dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
}
// 或者,如果使用 Compose
dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
}
2. 基本用法
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
class MyViewModel : ViewModel() {
fun loadData() {
// 在 viewModelScope 中启动协程
viewModelScope.launch {
// 模拟网络请求
delay(2000)
// 更新状态
_data.value = "加载完成"
}
}
fun loadDataWithException() {
viewModelScope.launch {
try {
val result = apiService.getData()
_data.value = result
} catch (e: Exception) {
_error.value = e.message
}
}
}
}
核心特性
1. 自动取消
class UserViewModel : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
// 长时间运行的任务
val usersFromApi = userRepository.getUsers()
_users.value = usersFromApi
// 如果 ViewModel 在 getUsers() 执行期间被销毁,
// 这个协程会自动取消,避免内存泄漏
}
}
// 即使有多个协程,也会在 ViewModel 销毁时全部取消
fun loadMultipleData() {
viewModelScope.launch {
launch { loadProfile() }
launch { loadFriends() }
launch { loadMessages() }
}
}
private suspend fun loadProfile() {
// 加载个人资料
delay(1000)
}
}
2. 默认使用 Main 调度器
class MyViewModel : ViewModel() {
fun updateUI() {
viewModelScope.launch {
// 默认在主线程执行,可以安全更新 UI
_loading.value = true
// 切换到 IO 线程执行耗时操作
val data = withContext(Dispatchers.IO) {
apiService.fetchData()
}
// 自动切换回主线程
_data.value = data
_loading.value = false
}
}
// 或者直接指定调度器
fun fetchData() {
viewModelScope.launch(Dispatchers.IO) {
val data = apiService.fetchData()
// 更新 UI 需要切回主线程
withContext(Dispatchers.Main) {
_data.value = data
}
}
}
}
高级用法
1. 并发处理
class ProductViewModel : ViewModel() {
fun loadProductDetails(productId: String) {
viewModelScope.launch {
// 并发执行多个请求
val detailsDeferred = async { productRepo.getDetails(productId) }
val reviewsDeferred = async { reviewRepo.getReviews(productId) }
val relatedDeferred = async { productRepo.getRelatedProducts(productId) }
try {
// 等待所有结果
val details = detailsDeferred.await()
val reviews = reviewsDeferred.await()
val related = relatedDeferred.await()
// 合并结果
_productState.value = ProductState.Success(
ProductData(details, reviews, related)
)
} catch (e: Exception) {
_productState.value = ProductState.Error(e.message ?: "Unknown error")
}
}
}
}
2. 超时处理
class TimeoutViewModel : ViewModel() {
fun loadWithTimeout() {
viewModelScope.launch {
try {
// 设置超时
val result = withTimeout(5000) {
apiService.fetchData() // 如果超过5秒,会抛出 TimeoutCancellationException
}
_data.value = result
} catch (e: TimeoutCancellationException) {
_error.value = "请求超时"
} catch (e: Exception) {
_error.value = "发生错误: ${e.message}"
}
}
}
}
3. 重试机制
class RetryViewModel : ViewModel() {
fun loadWithRetry() {
viewModelScope.launch {
var retryCount = 0
val maxRetries = 3
while (retryCount < maxRetries) {
try {
val result = apiService.fetchData()
_data.value = result
break // 成功则退出循环
} catch (e: IOException) {
retryCount++
if (retryCount == maxRetries) {
_error.value = "重试次数已达上限"
} else {
delay(1000 * retryCount) // 指数退避
}
}
}
}
}
// 使用 retry 库(需要添加依赖)
fun loadWithRetryLibrary() {
viewModelScope.launch {
val result = retry(
times = 3,
initialDelay = 1000,
maxDelay = 3000
) {
apiService.fetchData()
}
_data.value = result
}
}
}
实际应用场景
1. 表单提交
class RegistrationViewModel : ViewModel() {
private val _registrationState = MutableStateFlow<RegistrationState>(RegistrationState.Idle)
val registrationState: StateFlow<RegistrationState> = _registrationState.asStateFlow()
fun register(email: String, password: String) {
viewModelScope.launch {
_registrationState.value = RegistrationState.Loading
try {
val response = authRepository.register(email, password)
if (response.isSuccessful) {
_registrationState.value = RegistrationState.Success
// 自动登录
autoLogin(email, password)
} else {
_registrationState.value = RegistrationState.Error(response.message)
}
} catch (e: Exception) {
_registrationState.value = RegistrationState.Error(e.message ?: "注册失败")
}
}
}
private suspend fun autoLogin(email: String, password: String) {
try {
val token = authRepository.login(email, password)
// 保存 token 等操作
} catch (e: Exception) {
// 自动登录失败,但注册已经成功
}
}
sealed class RegistrationState {
object Idle : RegistrationState()
object Loading : RegistrationState()
object Success : RegistrationState()
data class Error(val message: String) : RegistrationState()
}
}
2. 分页加载
class PagingViewModel : ViewModel() {
private val _items = MutableStateFlow<List<Item>>(emptyList())
private val _isLoading = MutableStateFlow(false)
private val _hasMore = MutableStateFlow(true)
val items: StateFlow<List<Item>> = _items.asStateFlow()
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
val hasMore: StateFlow<Boolean> = _hasMore.asStateFlow()
private var currentPage = 0
private val pageSize = 20
fun loadMore() {
if (_isLoading.value || !_hasMore.value) return
viewModelScope.launch {
_isLoading.value = true
try {
val newItems = itemRepository.getItems(currentPage, pageSize)
if (newItems.isNotEmpty()) {
_items.value = _items.value + newItems
currentPage++
if (newItems.size < pageSize) {
_hasMore.value = false
}
} else {
_hasMore.value = false
}
} catch (e: Exception) {
// 处理错误
} finally {
_isLoading.value = false
}
}
}
fun refresh() {
viewModelScope.launch {
currentPage = 0
_items.value = emptyList()
_hasMore.value = true
loadMore()
}
}
}
3. 实时数据流处理
class StockViewModel : ViewModel() {
private val _stockPrices = MutableStateFlow<Map<String, Double>>(emptyMap())
val stockPrices: StateFlow<Map<String, Double>> = _stockPrices.asStateFlow()
private var stockJob: Job? = null
fun startStockUpdates() {
stopStockUpdates() // 停止之前的更新
stockJob = viewModelScope.launch {
stockRepository.getStockUpdates()
.catch { e ->
// 处理错误
_error.value = e.message
}
.collect { stockData ->
_stockPrices.value = stockData
}
}
}
fun stopStockUpdates() {
stockJob?.cancel()
stockJob = null
}
// ViewModel 销毁时自动取消
override fun onCleared() {
super.onCleared()
stopStockUpdates()
}
}
结合其他组件使用
1. 与 Room 数据库结合
class TaskViewModel(
private val taskDao: TaskDao
) : ViewModel() {
val tasks: LiveData<List<Task>> = taskDao.getAllTasks().asLiveData()
fun addTask(task: Task) {
viewModelScope.launch(Dispatchers.IO) {
taskDao.insert(task)
}
}
fun deleteTask(task: Task) {
viewModelScope.launch(Dispatchers.IO) {
taskDao.delete(task)
}
}
// 观察数据库变化
init {
viewModelScope.launch {
taskDao.getTaskCount()
.flowOn(Dispatchers.IO)
.collect { count ->
_taskCount.value = count
}
}
}
}
2. 与 Retrofit 网络请求结合
class WeatherViewModel(
private val weatherService: WeatherService
) : ViewModel() {
private val _weather = MutableStateFlow<Weather?>(null)
private val _forecast = MutableStateFlow<List<Forecast>>(emptyList())
val weather: StateFlow<Weather?> = _weather.asStateFlow()
val forecast: StateFlow<List<Forecast>> = _forecast.asStateFlow()
fun loadWeather(city: String) {
viewModelScope.launch {
try {
// 并发请求天气和预报
val weatherDeferred = async { weatherService.getCurrentWeather(city) }
val forecastDeferred = async { weatherService.getForecast(city) }
_weather.value = weatherDeferred.await()
_forecast.value = forecastDeferred.await()
} catch (e: Exception) {
_error.value = "获取天气失败: ${e.message}"
}
}
}
}
3. 在 Compose 中使用
@Composable
fun UserProfileScreen(
viewModel: UserProfileViewModel = viewModel()
) {
val userState by viewModel.userState.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
Column {
when (val state = userState) {
is UserState.Loading -> LoadingIndicator()
is UserState.Success -> UserProfileContent(state.user)
is UserState.Error -> ErrorMessage(state.message)
}
}
// 副作用:加载数据
LaunchedEffect(Unit) {
viewModel.loadUserProfile()
}
}
class UserProfileViewModel : ViewModel() {
private val _userState = MutableStateFlow<UserState>(UserState.Loading)
val userState: StateFlow<UserState> = _userState.asStateFlow()
fun loadUserProfile() {
viewModelScope.launch {
_userState.value = UserState.Loading
try {
val user = userRepository.getUserProfile()
_userState.value = UserState.Success(user)
} catch (e: Exception) {
_userState.value = UserState.Error(e.message ?: "Unknown error")
}
}
}
}
最佳实践
1. 避免重复调用
class SearchViewModel : ViewModel() {
private var searchJob: Job? = null
fun search(query: String) {
// 取消之前的搜索
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(300) // 防抖
if (query.length >= 2) {
val results = searchRepository.search(query)
_searchResults.value = results
}
}
}
}
2. 管理多个协程
class DashboardViewModel : ViewModel() {
private val jobs = mutableListOf<Job>()
fun loadDashboardData() {
// 清除之前的任务
jobs.forEach { it.cancel() }
jobs.clear()
// 启动多个任务
jobs.add(viewModelScope.launch { loadStats() })
jobs.add(viewModelScope.launch { loadRecentActivity() })
jobs.add(viewModelScope.launch { loadNotifications() })
}
fun cancelAll() {
jobs.forEach { it.cancel() }
jobs.clear()
}
}
3. 错误处理统一管理
abstract class BaseViewModel : ViewModel() {
protected val _error = MutableStateFlow<String?>(null)
val error: StateFlow<String?> = _error.asStateFlow()
protected fun <T> tryExecute(
block: suspend () -> T,
onSuccess: (T) -> Unit = {},
onError: (Exception) -> Unit = { _error.value = it.message }
) {
viewModelScope.launch {
try {
val result = block()
onSuccess(result)
} catch (e: Exception) {
onError(e)
}
}
}
}
class MyViewModel : BaseViewModel() {
fun loadData() {
tryExecute(
block = { apiService.getData() },
onSuccess = { data -> _data.value = data }
)
}
}
4. 测试 ViewModelScope
// 测试时需要替换 Dispatchers.Main
@ExperimentalCoroutinesApi
class MyViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule() // 自定义测试规则
@Test
fun `test data loading`() = runTest {
val viewModel = MyViewModel()
viewModel.loadData()
// 验证数据加载逻辑
advanceUntilIdle() // 等待协程完成
assertEquals(expectedData, viewModel.data.value)
}
}
@ExperimentalCoroutinesApi
class MainDispatcherRule : TestWatcher() {
override fun starting(description: Description) {
super.starting(description)
Dispatchers.setMain(StandardTestDispatcher())
}
override fun finished(description: Description) {
super.finished(description)
Dispatchers.resetMain()
}
}
原理深入
1. ViewModelScope 的实现
// 简化的实现原理
val ViewModel.viewModelScope: CoroutineScope
get() {
// 检查是否已有 scope
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
// 创建新的 scope
return setTagIfAbsent(
JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
)
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
// ViewModel 销毁时
override fun onCleared() {
super.onCleared()
// 获取 scope 并关闭它
val scope = getTag(JOB_KEY) as? CloseableCoroutineScope
scope?.close()
}
2. 生命周期绑定
class MyViewModel : ViewModel() {
init {
// 初始化时启动协程
viewModelScope.launch {
// 这个协程会在 ViewModel 销毁时自动取消
collectDataFromFlow()
}
}
private suspend fun collectDataFromFlow() {
dataFlow
.catch { e ->
// 处理错误
}
.collect { data ->
// 处理数据
_data.value = data
}
}
// 手动控制的生命周期
private var manualScope: CoroutineScope? = null
fun startManualTask() {
manualScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
manualScope?.launch {
// 需要手动管理生命周期
}
}
override fun onCleared() {
super.onCleared()
manualScope?.cancel() // 必须手动取消
}
}
3. SupervisorJob 的作用
// viewModelScope 使用 SupervisorJob,而不是普通的 Job
// 这意味着一个子协程的失败不会影响其他子协程
class SupervisorExampleViewModel : ViewModel() {
fun startMultipleTasks() {
viewModelScope.launch {
// 任务1:如果失败,不会影响任务2
launch {
try {
task1()
} catch (e: Exception) {
println("Task 1 failed: $e")
}
}
// 任务2:会继续执行
launch {
task2()
}
// 任务3:也会继续执行
launch {
task3()
}
}
}
private suspend fun task1() {
delay(100)
throw RuntimeException("Task 1 failed!")
}
private suspend fun task2() {
delay(200)
println("Task 2 completed")
}
private suspend fun task3() {
delay(300)
println("Task 3 completed")
}
}
常见问题
1. 协程未被取消
class ProblemViewModel : ViewModel() {
fun problematicTask() {
viewModelScope.launch {
// 这个循环不会检查协程是否被取消
while (true) {
println("Running...")
delay(1000) // ✅ delay 是可取消的挂起函数
}
}
}
fun anotherProblematicTask() {
viewModelScope.launch {
// 这个循环不会响应取消
var i = 0
while (true) {
i++ // ❌ 纯计算,不会检查取消
// 解决方案:定期检查取消状态
ensureActive() // ✅ 检查协程是否活跃
// 或者使用 yield()
yield() // ✅ 让出线程,检查取消
}
}
}
}
2. 内存泄漏
class LeakyViewModel : ViewModel() {
private val someListener = object : SomeListener {
override fun onEvent(data: String) {
// 持有 ViewModel 引用,可能导致泄漏
}
}
// 解决方案:使用弱引用或 viewModelScope
private val safeListener = object : SomeListener {
override fun onEvent(data: String) {
viewModelScope.launch {
// 在 ViewModelScope 中处理,安全
handleEvent(data)
}
}
}
private suspend fun handleEvent(data: String) {
// 处理事件
}
}
总结:
viewModelScope是 ViewModel 的扩展属性,提供生命周期感知的协程作用域- 自动管理:ViewModel 销毁时,所有协程自动取消
- 默认在主线程:方便更新 UI,但耗时操作需要切到其他调度器
- 使用 SupervisorJob:子协程失败不影响其他子协程
- 最佳实践:避免重复调用、统一错误处理、注意协程取消检查
- 测试:需要替换 Dispatchers.Main 进行单元测试
使用 viewModelScope 可以安全地在 ViewModel 中执行异步操作,是现代 Android 架构中推荐的方式。