Hello hope someone can help me.
Problem: In code #1. The code inside "onListenerDisconnected" will not finish executing because the coroutine will be cancelled via onDestroy.
code #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()
}
}
}
My defective Solution: Use a coroutine that outlive the service lifecycle which in this case I used a coroutine that is attached to the application lifecycle. but when I do this leak canary detects memory leak says that NotificationService is leaking
@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
)
)
}
}
}
Question:
- Should I be concerned with the memory leak that leak canary detects or is this just a small price and can be ignored to achieve what i want to do?
┬───
│ 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
- If this memory leak cant be ignored what other way can I do this without memory leak?
2
Answers
Thanks to @Tenfour04 that gave me an idea on how to solve this. Instead of injecting the lifecyclescope inside the service I injected it inside the usecase. I have verified it with leak canary and it says all retained objects are garbage collected
I think your problem in the second solution with the injected CoroutineScope is that the coroutine is capturing a reference to your whole class because it uses the
updateIsNotificationServiceActiveUseCase
property of that class. So, copy the value of that property to a local variable before you launch the coroutine and use only the local variable in the coroutine:"
lifeCycleScope
" seems to be a misleading name for this scope since you explicitly designed it to outlive this class’s lifecycle.