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

Android NotificationService协程内存泄漏与任务执行保障方案咨询

如何在执行超出调用者生命周期的代码时避免内存泄漏?

问题背景

在代码#1中,onListenerDisconnected内的代码无法执行完成,因为Service销毁时协程会被取消。

代码#1

@AndroidEntryPoint
class NotificationService : NotificationListenerService() {

    @Inject
    lateinit var updateIsNotificationServiceActiveUseCase: UpdateIsNotificationServiceActiveUseCase

    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.IO + job)

    override fun onListenerConnected() {
        scope.launch {
            updateIsNotificationServiceActiveUseCase.execute(
                UpdateIsNotificationServiceActiveUseCase.Companion.Param(
                    isActive = true
                )
            )
        }
    }

    override fun onListenerDisconnected() {
        scope.launch {
            updateIsNotificationServiceActiveUseCase.execute(
                UpdateIsNotificationServiceActiveUseCase.Companion.Param(
                    isActive = false
                )
            )
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        if (job.isActive) {
            job.cancel()
        }
    }
}

尝试的有缺陷解决方案

为了让代码能执行完成,我尝试使用绑定到应用生命周期的CoroutineScope(而非Service自己的Scope),但LeakCanary检测到NotificationService存在内存泄漏。

对应代码

@AndroidEntryPoint
class NotificationService : NotificationListenerService() {

    @Inject
    lateinit var updateIsNotificationServiceActiveUseCase: UpdateIsNotificationServiceActiveUseCase

    @Inject
    lateinit var lifeCycleScope: CoroutineScope

    override fun onListenerConnected() {
        lifeCycleScope.launch {
            updateIsNotificationServiceActiveUseCase.execute(
                UpdateIsNotificationServiceActiveUseCase.Companion.Param(
                    isActive = true
                )
            )
        }
    }

    override fun onListenerDisconnected() {
        lifeCycleScope.launch {
            updateIsNotificationServiceActiveUseCase.execute(
                UpdateIsNotificationServiceActiveUseCase.Companion.Param(
                    isActive = false
                )
            )
        }
    }
}

核心问题

  1. 我是否需要关注LeakCanary检测到的内存泄漏?还是可以为了实现需求而忽略该问题?

LeakCanary检测报告:

┬───
│ GC Root: Global variable in native code
│
├─ android.service.notification.
│  NotificationListenerService$NotificationListenerWrapper instance
│    Leaking: UNKNOWN
│    Retaining 2.5 kB in 35 objects
│    this$0 instance of link.limecode.histotify.services.NotificationService
│    ↓ NotificationListenerService$NotificationListenerWrapper.this$0
│                                                              ~~~~~~
╰→ link.limecode.histotify.services.NotificationService instance
​     Leaking: YES (ObjectWatcher was watching this because link.limecode.
​     histotify.services.NotificationService received Service#onDestroy()
​     callback and Service not held by ActivityThread)
​     Retaining 1.9 kB in 34 objects
​     key = 1532cc3d-e8d6-40dc-81d4-914260acb10d
​     watchDurationMillis = 18592
​     retainedDurationMillis = 13584
​     mApplication instance of link.limecode.histotify.HistotifyApp
​     mBase instance of android.app.ContextImpl
 
METADATA
 
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: HUAWEI
LeakCanary version: 2.10
App process name: link.limecode.histotify
Class count: 16160
Instance count: 130077
Primitive array count: 93652
Object array count: 18860
Thread count: 26
Heap total bytes: 17295269
Bitmap count: 11
Bitmap total bytes: 8450411
Large bitmap count: 0
Large bitmap total bytes: 0
Db 1: open /data/user/0/link.limecode.histotify/databases/leaks.db
Stats: LruCache[maxSize=3000,hits=34712,misses=89445,hitRate=27%]
RandomAccess[bytes=4417411,reads=89445,travel=29347908102,range=21153586,size=26
687475]
Analysis duration: 7242 ms
  1. 如果该内存泄漏不可忽略,还有什么方法可以在无内存泄漏的前提下实现需求?

回答

问题1:是否需要关注该内存泄漏?

必须关注,不能忽略。从LeakCanary报告来看,NotificationService实例在onDestroy后仍被NotificationListenerWrapper(Native层全局引用)持有,会导致该实例无法被GC回收。虽然单次泄漏内存不大,但如果Service频繁创建销毁,累计泄漏会逐步占用更多内存,最终可能引发OOM,同时也违背Android内存管理的最佳实践。

另外需要注意:使用应用级Scope本身不会直接导致泄漏,但这里的泄漏根源是NotificationListenerService内部的NotificationListenerWrapper持有Service实例引用,应用Scope的协程可能间接延长了引用生命周期,或者这是特定厂商(华为Android 29)的系统层面问题,但无论哪种情况,都应该处理以避免潜在风险。

问题2:无内存泄漏的实现方案

要在Service销毁后仍能完成updateIsNotificationServiceActiveUseCase.execute的执行,同时避免内存泄漏,推荐以下几种方案:

方案1:线程池执行同步任务

如果execute方法可以改为同步执行(或内部封装为同步),直接用线程池执行,避免持有Service引用:

@AndroidEntryPoint
class NotificationService : NotificationListenerService() {

    @Inject
    lateinit var updateIsNotificationServiceActiveUseCase: UpdateIsNotificationServiceActiveUseCase

    // 应用级线程池,避免重复创建线程
    private val executor = Executors.newSingleThreadExecutor()

    override fun onListenerConnected() {
        executor.execute {
            updateIsNotificationServiceActiveUseCase.execute(
                UpdateIsNotificationServiceActiveUseCase.Companion.Param(isActive = true)
            )
        }
    }

    override fun onListenerDisconnected() {
        executor.execute {
            updateIsNotificationServiceActiveUseCase.execute(
                UpdateIsNotificationServiceActiveUseCase.Companion.Param(isActive = false)
            )
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 优雅关闭线程池,等待已提交任务完成
        executor.shutdown()
    }
}

注:executor.shutdown()不会中断已执行任务,只会拒绝新任务,确保onListenerDisconnected中的任务能完成。

方案2:独立CoroutineScope + 避免持有Service引用

如果必须用协程,创建不依赖Service生命周期的Scope,同时确保UseCase不持有Service引用(所有Context依赖使用Application Context):

@AndroidEntryPoint
class NotificationService : NotificationListenerService() {

    @Inject
    lateinit var updateIsNotificationServiceActiveUseCase: UpdateIsNotificationServiceActiveUseCase

    // 创建独立Scope,不绑定Service生命周期
    private val globalJob = SupervisorJob()
    private val globalScope = CoroutineScope(Dispatchers.IO + globalJob)

    override fun onListenerConnected() {
        globalScope.launch {
            updateIsNotificationServiceActiveUseCase.execute(
                UpdateIsNotificationServiceActiveUseCase.Companion.Param(isActive = true)
            )
        }
    }

    override fun onListenerDisconnected() {
        globalScope.launch {
            updateIsNotificationServiceActiveUseCase.execute(
                UpdateIsNotificationServiceActiveUseCase.Companion.Param(isActive = false)
            )
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 不取消Job,让已启动的协程完成执行
    }
}

关键:确保updateIsNotificationServiceActiveUseCase内部无NotificationService引用,所有Context依赖用Application Context,避免协程执行时持有Service实例。

方案3:WorkManager(推荐用于需保证执行的任务)

如果需要保证更新操作即使应用进程重启也能完成,使用WorkManager提交一次性任务:

@AndroidEntryPoint
class NotificationService : NotificationListenerService() {

    @Inject
    lateinit var workManager: WorkManager

    override fun onListenerConnected() {
        val request = OneTimeWorkRequestBuilder<UpdateNotificationStatusWorker>()
            .setInputData(workDataOf("is_active" to true))
            .build()
        workManager.enqueue(request)
    }

    override fun onListenerDisconnected() {
        val request = OneTimeWorkRequestBuilder<UpdateNotificationStatusWorker>()
            .setInputData(workDataOf("is_active" to false))
            .build()
        workManager.enqueue(request)
    }
}

// Worker实现类
class UpdateNotificationStatusWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    @Inject
    lateinit var updateIsNotificationServiceActiveUseCase: UpdateIsNotificationServiceActiveUseCase

    init {
        // 初始化依赖注入
        DaggerAppComponent.factory().create(applicationContext).inject(this)
    }

    override suspend fun doWork(): Result {
        val isActive = inputData.getBoolean("is_active", false)
        return try {
            updateIsNotificationServiceActiveUseCase.execute(
                UpdateIsNotificationServiceActiveUseCase.Companion.Param(isActive = isActive)
            )
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

WorkManager完全不依赖Service生命周期,也不会持有Service引用,是最安全的方案之一,适合需要保证执行的任务。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 13:07:11