12.3. Remote bound services

Remote services can be run from applications other than those containing them. In this case, if they run from the same application that contains them, they do stop when the application is closed. However, if they run from another application, the service still runs even if the application that made the request stops.

We declare a MyServiceRemote class that inherits from Service. This class has an internal class that inherits from Handler. In this class, the messages for the methods we want to run will be received.

class MyServiceRemote : Service() {

   inner class MessagesHandler : Handler(Looper.getMainLooper()) {
    
       protected lateinit var mediaPlayer: MediaPlayer

       override fun handleMessage(msg: Message) {
           when (msg.what) {
               MSG_PLAY_SOUND -> {
                   mediaPlayer = MediaPlayer.create(this@MyServiceRemote, R.raw.sound1)
                   mediaPlayer?.start()

                   val responseMessage:Message = Message.obtain(null,MSG_PLAY_SOUND);
                   responseMessage.arg1 = 100

                   msg.replyTo.send(responseMessage)
               }
               else -> super.handleMessage(msg)
           }
       }
   }


   val mMessenger: Messenger = Messenger(MessagesHandler())

   override fun onBind(p0: Intent?): IBinder? {
       return mMessenger.getBinder();
   }

}

We need to declare this service in the manifest.xml file:

<service
   android:name=".MyServiceRemote"
   android:enabled="true"
   android:exported="true" />

For remote services, it is very important that the android:exported attribute is assigned to true because we want to export the service for use from an external application.

In the main activity we declare an inner class ReceiveHandler where we will receive the answers to our service calls.

inner class ReceiveHandler : Handler(Looper.getMainLooper()) {
   override fun handleMessage(msg: Message) {
      Log.d("Response",msg.arg1.toString())
   }
}

We also declare a static object of type ServiceConnection that will allow us to create the instances of the Messenger classes: one to send the method execution requests and another to receive the answers.

val scRemote = object: ServiceConnection {

   override fun onServiceConnected(className: ComponentName, service: IBinder) {
       mRemoteService  = Messenger(service)
       mRemoteServiceReceiveMessenger = Messenger(ReceiveHandler())

   }

   override fun onServiceDisconnected(arg0: ComponentName) {

   }
}

We can now create the service. To create the service, we must indicate the name of the package containing it and the full name of the service.

intent  = Intent()
intent.setComponent(ComponentName("com.uoc.services", "com.uoc.services.MyServiceRemote"))
bindService(intent, scRemote, BIND_AUTO_CREATE)

Once the service has been created, we can invoke its methods. In this case, we indicate the MSG_PLAY_SOUND method. In the Message we can add additional parameters. We indicate in the replyTo the property of the Activity that contains the Messenger where we want to receive the responses.

msec value: Message = Message.obtain(null, MSG_PLAY_SOUND, 0, 0)
msg.replyTo = mRemoteServiceReceiveMessenger
mRemoteService?.send(msg)

Within the service, the method handleMessage is invoked. Depending on the what property of Message, one code or another is executed. In this case, the following is executed:

override fun handleMessage(msg: Message) {
   when (msg.what) {
       MSG_PLAY_SOUND -> {
           mediaPlayer = MediaPlayer.create(this@MyServiceRemote, R.raw.sound1)
           mediaPlayer?.start()

           val responseMessage:Message = Message.obtain(null,MSG_PLAY_SOUND);
           responseMessage.arg1 = 100

           msg.replyTo.send(responseMessage)
       }
       else -> super.handleMessage(msg)
   }
}

After starting to play the audio file, a response message is created with the following snippet:

val responseMessage:Message = Message.obtain(null,MSG_PLAY_SOUND);
  responseMessage.arg1 = 100
  msg.replyTo.send(responseMessage)

That response message will reach the ReceiveHandler that we declared in our main activity.

inner class ReceiveHandler : Handler(Looper.getMainLooper()) {
   override fun handleMessage(msg: Message) {
      Log.d("Response",msg.arg1.toString())
   }
}

Remember that, in this scenario, even if we destroy the application that invoked the service remotely (that is, it is a different application) the service will continue to run. That is, in this case the audio file will continue playing.

Learn more: Messages to a remote service are queued to a message queue. These messages run one after the other on a first-come, first-served basis. In the event that multiple applications use the same service or we generally want more than one message to run at a time, we have to use concurrent programming techniques.

Invoking a remote service. Invoking a remote service.
Source: Javier Salvador (Original image) License: CC BY-NC-ND 4.0