Enrico
Enrico

Reputation: 10665

Bound service leaks memory

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)

Upvotes: 11

Views: 2103

Answers (2)

Ercilan
Ercilan

Reputation: 11

I am also confused about this with leakCanary. If you dump the heap and import into Android Studio, you will find that the instance and related object does exist but the AS not showing memory leak("0 classes"??):

image: 28 Allocations here

Now i found out a better way to solve this. Use the static inner class to avoid implicitly holding a outer class intance but hold it with WeakReference:

class MyService : Service() {
    private val binder by lazy { MyServiceBinder(this) }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }
    
    // ...
    
    companion object {
        class MyServiceBinder(service: MyService) : Binder() {
            private val serviceRef: WeakReference<MyService> = WeakReference(service)

            fun getService(): MyService? = serviceRef.get()
        }
    }
}

After doing this, the leakcanary dose not show warnings and the heap dump not shows remaining and other related instances:

image: 10 Allocations here

Upvotes: 0

Rookie
Rookie

Reputation: 334

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.

Upvotes: 9

Related Questions