You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

viewModelScope.launch运行异常问题咨询(Dagger-Hilt场景)

问题描述

示例1

首次打开主Activity时,MainActivityViewModel中的viewModelScope.launch运行正常;但打开第二个Activity并关闭主Activity后,再次打开主Activity时,viewModelScope.launch因viewModelScope未激活而无法运行。

示例2

首次打开主Activity时,viewModelScope.launch运行正常;但打开第二个Activity且不关闭主Activity,再次打开主Activity时,viewModelScope.launch会继续之前的任务同时启动新任务。

技术栈

使用Dagger-Hilt进行依赖注入

需求

  1. 实现每次打开主Activity时都能启动viewModelScope.launch,并找出示例1的问题原因;
  2. 在打开第二个Activity且不关闭主Activity时,停止viewModelScope.launch的运行。

解决方案

问题1:示例1原因分析与修复

原因

调用finish()关闭MainActivity时,ViewModel会被系统销毁,其viewModelScope也会被标记为非激活状态。若出现重建后协程无法启动的情况,核心原因是ViewModel实例未被正确重建——可能是Hilt配置错误将ViewModel设为单例,或手动使用ViewModelProvider时未正确关联Hilt的Factory,导致复用了已销毁的ViewModel实例。

修复步骤

  1. 检查Hilt配置,确保@HiltViewModel注解的MainActivityViewModel未被错误绑定为单例。
  2. 在Activity中使用Hilt提供的ViewModelFactory获取实例,替代手动无参ViewModelProvider:
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
    private MainActivityViewModel viewModel;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // 正确使用Hilt的ViewModelFactory
        viewModel = new ViewModelProvider(this, DefaultViewModelProviderFactory.getInstance(this))
                .get(MainActivityViewModel.class);

        viewModel.testOneViewModelScopeMA();

        binding.btnSecondActivity.setOnClickListener(view -> 
            startSecondActivityIntentWithFinishCurrentActivity(MainActivity.this)
        );
        ...
    }

    private void startSecondActivityIntentWithFinishCurrentActivity(Context _context) {
        Intent intent = new Intent(_context, SecondActivity.class);
        _context.startActivity(intent);
        finish();
    }
}
  1. 在ViewModel方法中添加激活状态判断,确保协程仅在有效状态下启动:
fun testOneViewModelScopeMA() {
    Timber.d("-> testOneViewModelScope, \nviewModelScope is active: ${viewModelScope.isActive}")
    if (viewModelScope.isActive) {
        viewModelScope.launch {
            try {
                var count = 0
                while (true) {
                    delay(1000L)
                    count += 1
                    Timber.d(
                        "-> testOneViewModelScope, " +
                                "\nviewModelScope is active: ${viewModelScope.isActive} " +
                                "\noutput: $count "
                    )
                }
            } catch (e: CancellationException) {
                Timber.e(
                    "-> testOneViewModelScope, " +
                            "\nviewModelScope is active: ${viewModelScope.isActive} " +
                            "\nCancellationException: \n${e.localizedMessage}"
                )
                throw e
            } catch (ex: Exception) {
                Timber.e(
                    "-> testOneViewModelScope, " +
                            "\nviewModelScope is active: ${viewModelScope.isActive} " +
                            "\nException: \n${ex.localizedMessage}"
                )
            }
        }
    } else {
        Timber.w("-> testOneViewModelScope: viewModelScope is not active, cannot launch coroutine")
    }
}

问题2:示例2的修复(切换Activity时停止协程)

思路

通过保存协程的Job引用,在Activity进入后台时主动取消任务,避免重复启动或后台运行。

修复步骤

  1. 在ViewModel中维护协程Job引用,并提供取消方法:
@HiltViewModel
class MainActivityViewModel @Inject constructor(
    private val mainActivityRepository: MainActivityRepository,
    @IoDispatcher private val ioDispatcher: CoroutineDispatcher,
    @MainDispatcher private val mainDispatcher: CoroutineDispatcher,
    @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) : ViewModel() {
    private var coroutineJob: Job? = null

    fun testOneViewModelScopeMA() {
        // 先取消已有任务,避免重复启动
        coroutineJob?.cancel()
        Timber.d("-> testOneViewModelScope, \nviewModelScope is active: ${viewModelScope.isActive}")
        coroutineJob = viewModelScope.launch {
            try {
                var count = 0
                while (true) {
                    delay(1000L)
                    count += 1
                    Timber.d(
                        "-> testOneViewModelScope, " +
                                "\nviewModelScope is active: ${viewModelScope.isActive} " +
                                "\noutput: $count "
                    )
                }
            } catch (e: CancellationException) {
                Timber.e(
                    "-> testOneViewModelScope, " +
                            "\nviewModelScope is active: ${viewModelScope.isActive} " +
                            "\nCancellationException: \n${e.localizedMessage}"
                )
                throw e
            } catch (ex: Exception) {
                Timber.e(
                    "-> testOneViewModelScope, " +
                            "\nviewModelScope is active: ${viewModelScope.isActive} " +
                            "\nException: \n${ex.localizedMessage}"
                )
            } finally {
                coroutineJob = null
            }
        }
    }

    // 供Activity调用,取消运行中的协程
    fun cancelRunningCoroutine() {
        coroutineJob?.cancel()
        coroutineJob = null
    }
}
  1. 在Activity的onStop方法中调用取消逻辑:
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
    private MainActivityViewModel viewModel;
    ...
    @Override
    protected void onStop() {
        super.onStop();
        // Activity进入后台时取消协程
        viewModel.cancelRunningCoroutine();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        viewModel = new ViewModelProvider(this, DefaultViewModelProviderFactory.getInstance(this))
                .get(MainActivityViewModel.class);

        viewModel.testOneViewModelScopeMA();

        binding.btnSecondActivity.setOnClickListener(view -> 
            startSecondActivityIntentWithOutFinishCurrentActivity(MainActivity.this)
        );
        ...
    }

    private void startSecondActivityIntentWithOutFinishCurrentActivity(Context _context) {
        Intent intent = new Intent(_context, SecondActivity.class);
        _context.startActivity(intent);
    }
}

补充说明

  • 若需Activity回到前台时自动重启协程,可在onResume方法中再次调用viewModel.testOneViewModelScopeMA()。
  • viewModelScope会在ViewModel销毁时自动取消所有协程,无需额外处理内存泄漏。

内容的提问来源于stack exchange,提问作者DevPeter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 11:37:06