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

如何避免Android Job并行运行,保证同一时间仅单个实例执行

问题根因
  • Mutex实例不共享:每次Job被触发时Android Job框架都会创建新的SendCertificatesJob实例,你在类内定义的private val mutex是实例成员,每个Job实例各持一把独立的锁,自然不会触发互斥等待逻辑
  • 调度配置冲突:你当前定时任务用TAG、即时任务用TAG_NOW两个不同的任务标记,setUpdateCurrent(true)仅会替换同标记的待执行任务,不同标记的任务会被独立调度,这也是并行问题的诱因之一
解决方案

1. 实现Mutex全局单例

由于你用了依赖注入框架,可以直接把Mutex定义为全局单例,所有Job实例共享同一把锁即可:

// 方案1:直接定义全局单例锁,仅用于该任务互斥,实现最简单
object SendCertificatesLock {
    val mutex = Mutex()
}

// 方案2:如果要统一通过DI注入,在注入模块中声明单例
@Module
@InstallIn(SingletonComponent::class)
object LockModule {
    @Provides
    @Singleton
    @Named("SendCertificatesMutex")
    fun provideSendCertificatesMutex() = Mutex()
}

删除Job类中原有的实例级mutex和countDownLatch变量,改为注入/直接引用全局单例锁。

2. 简化协程执行逻辑

你原代码中用GlobalScope.launch加CountDownLatch等待的写法完全没必要,可直接用runBlocking配合Mutex的withLock扩展方法实现自动加解锁、自动等待前序任务:

override fun onRunJob(params: Params): Result {
    // 原有API可用性校验、通知初始化、失败次数判断逻辑保持不变
    if (!CommonUtils.isApiEnabled(context))
        return Result.SUCCESS
    // ...省略原有前置逻辑

    return runBlocking(Dispatchers.IO) {
        // 直接加全局共享锁,前序任务未执行完时会自动挂起等待
        SendCertificatesLock.mutex.withLock {
            var jobResult = Result.SUCCESS
            userLogger.writeLogToFile("SendCertificatesJob.onRunJob(), date:" + Calendar.getInstance().time)
            try {
                // 原有业务逻辑直接放在此处即可,包括测试循环、健康检查、同步逻辑
                //Test
                var doIt = true
                var count =0
                while (doIt){
                    Timber.d("SendSyncWorker: $count")
                    count++
                    delay(10000)
                    if(count == 12)
                        doIt = false
                }
                healthCheckApi.checkHealth(ApiModule.API_KEY).await()
                try {
                    sendSync.syncRecordedClients()
                } catch (e: Exception) {
                    e.printStackTrace()
                }
                val result = sendSync().forEachParallel2()
                result.firstOrNull { it.second != null }?.let { throw Exception(it.second) }
                
                // 原有成功通知、缓存更新逻辑保持不变
                // ...
            } catch (e: Exception) {
                Timber.tag(TAG).e(e)
                // 原有失败通知逻辑保持不变
                // ...
                jobResult = Result.RESCHEDULE
            }
            return@runBlocking jobResult
        }
    }
}

withLock扩展会自动处理异常场景的锁释放,不需要手动调用lock/unlock,避免死锁风险。

3. 调度逻辑可选优化

如果你不需要保留定时、即时任务的独立调度队列,可以把两类任务的TAG统一,配合setUpdateCurrent(true)可以直接过滤掉重复的待执行任务,减少不必要的任务排队。

兜底兼容

如果需要应对进程被杀、锁状态丢失的极端场景,可以在SharedPreferences中新增一个is_job_running标记,任务启动时先写入标记为true,任务结束(无论成功失败)时重置为false,和Mutex配合做双重校验即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 15:15:00