Bound service leaks memory
Asked Answered
O

1

10

I wrote a basic bound service based on the Android documentation, but LeakCanary is telling me the service is leaking.

  1. Is there a leak or have I misconfigured LeakCanary?
  2. How can I write a bound service that does not leak?

The Code

class LocalService : Service() {

  private val binder = LocalBinder()
  private val generator = Random()

  val randomNumber: Int
    get() = generator.nextInt(100)

  inner class LocalBinder : Binder() {
    fun getService(): LocalService = this@LocalService
  }

  override fun onBind(intent: Intent): IBinder {
    return binder
  }

  override fun onDestroy() {
    super.onDestroy()
    LeakSentry.refWatcher.watch(this) // Only modification is to add LeakCanary
  }
}

If I bind to the service from an activity as follows, LeakCanary detects the service has leaked

class MainActivity: Activity() {

  private var service: LocalService? = null
  private val serviceConnection = object: ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
      service = (binder as LocalBinder).getService()
    }
    override fun onServiceDisconnected(name: ComponentName?) {
      service = null
    }
  }

  override fun onStart() {
    super.onStart()
    bindService(Intent(this, LocalService::class.java), serviceConnection, BIND_AUTO_CREATE)
  } 

  override fun onStop() {
    super.onStop()
    service?.let {
      unbindService(serviceConnection)
      service = null
    }
  }
}
┬
├─ com.example.serviceleak.LocalService$LocalBinder
│    Leaking: NO (it's a GC root)
│    ↓ LocalService$LocalBinder.this$0
│                               ~~~~~~
╰→ com.example.serviceleak.LocalService
​     Leaking: YES (RefWatcher was watching this)
Organogenesis answered 22/5, 2019 at 8:15 Comment(5)
There is no any flag BIND_AUTO_START in Context class...why you bind - unbind service in onCreate - onDestroy methods respectively?Roberto
You're right, it's a typo. The flag should be BIND_AUTO_CREATE. Whether the service is bound and unbound in onCreate and onDestroy is irrelevant; It leaks no matter which lifecycle methods you use to bind and unbind. But I'll change my example to match the Android docs.Organogenesis
Can you try ... moving Binder class to its own, separate class instead of an inner class and moving the random number generator to that class. Then inside the service, just instantiate it as a field, and return it in onBind(). I'm thinking maybe LeakCanary thinks it's a leak because the an binder inner class holds a reference to the outer class (the service) and that a reference to the binder is being held by another component with it's own lifecycle (the activity).Lawford
Inner class is the problem: https://mcmap.net/q/56712/-static-inner-class-in-kotlinVoluble
@JánosSicz-Mesziár I agree, but nothing should be holding a reference to the binder once the service is destroyedOrganogenesis
T
8

I don't know if it's late to answer but after reading your question I also setup leakCanary in my project and found this leak. I was sure that it's because of the inner binder class which is holding the reference of outer class which is service here. That is why in your leak log it shows LocationService is leaking. I found a solution by @commonsguy here and implemented the solution with a bit simpler example here. Hope this helps. Keep coding, stay blessed.

Tetratomic answered 30/5, 2019 at 10:53 Comment(3)
What is the significant change of the code from the asker to fix the leak?Fructify
How are you supposed to access the service object if the binder is external? The binder being an inner class is how it's done in android docs as well.Hostility
@Sujit: I likely could find the solution for this. Keep the explicit reference to the service in the binder instance. When the service is destroying explicitly destroy the binder instance and set the service reference to null there.Fructify

© 2022 - 2024 — McMap. All rights reserved.