Get notified when bound client is dead

In Android, a client can bind to a Service running in a different process and even a different APK, but how could the bound service know the remote client is killed by the system and release the resources?

Death recipients

The Android platform provides a “link to death” facility through the Binder framework to allow a Service to be notified when any remote client is dead. To get notified, the Service needs to:

  1. Obtains a reference to a Binder object that lives in the remote client.
  2. Calls the linkToDeath() method of the Binder object to register a DeathRecipient callback.
  3. When the remote client is dead, the binderDied() method of the callback will be called.

Note that we will only receive death notifications for remote clients, because local clients can only die when the service is also dead.

Show me the code

First, let’s define an AIDL interface to pass the Binder object to the server:

package com.example.server;

interface IRemoteService {
    void register(in IBinder client);
}

Alternatively, we can also use Messenger to pass the Binder object.

Next, we extend the remote Service to accept the Binder object and register the callback:

class RemoteService : Service() {
    private val binder = object : IRemoteService.Stub() {
        override fun register(client: IBinder?) {
            client?.linkToDeath({
                // This will be called when the remote client is dead.
                // Do the clean-ups you need.
            }, 0)
        }
    }

    override fun onBind(intent: Intent?): IBinder = binder

    ...
}

Now, the client can bind to the service and pass a Binder object:

class MainActivity : Activity() {
    private val binder = Binder()

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            IRemoteService.Stub.asInterface(service).register(binder)

            ...
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            ...
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val intent = Intent().apply {
            component = ComponentName("com.example.server", "com.example.server.RemoteService")
        }
        bindService(intent.setComp, connection, 0)

        ...
    }

    ...
}

Note that if the client targets Android 11 (API level 30) and above, and tries to connect to a remote service defined in another APK, we need to declare the package visibility needs. Otherwise, it will fail to bind the remote service with bindService() returning false.

That’s it. Happy hacking!


See also

comments powered by Disqus