An Android Remote Bound Service Tutorial

In this final chapter dedicated to Android services, an example application will be developed to demonstrate the use of a messenger and handler configuration to facilitate interaction between a client and remote bound service of a messenger and handler configuration to facilitate interaction between a client and a remote bound service.

Client to Remote Service Communication

As outlined in the previous chapter, the interaction between a client and a local service can be implemented by returning to the client an IBinder object containing a reference to the service object. In the case of remote services, however, this approach does not work because the remote service is running in a different process and, as such, cannot be reached directly from the client.

In the case of remote services, a Messenger and Handler configuration must be created, which allows messages to be passed across process boundaries between client and service.

Specifically, the service creates a Handler instance that will be called when a message is received from the client. In terms of initialization, it is the job of the Handler to create a Messenger object which, in turn, creates an IBinder object to be returned to the client in the onBind() method. The client uses This IBinder object to create an instance of the Messenger object and, subsequently, to send messages to the service handler. Each time a message is sent by the client, the handleMessage() method of the handler is called, passing through the message object.

The example created in this chapter will consist of an activity and a bound service running in separate processes. The Messenger/Handler mechanism will send a string to the service, which will display in the Logcat output.

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

Creating the Example Application

Select the New Project option from the welcome screen and, within the resulting new project dialog, choose the Empty Views Activity template before clicking on the Next button.

Enter RemoteBound into the Name field and specify com.ebookfrenzy.remotebound as the package name. Before clicking on the Finish button, change the Minimum API level setting to API 26: Android 8.0 (Oreo) and the Language menu to Kotlin.

Designing the User Interface

Locate the activity_main.xml file in the Project tool window and double-click on it to load it into the Layout Editor tool. With the Layout Editor tool in Design mode, right-click on the default TextView instance, choose the Convert view… menu option, select the Button view from the resulting dialog and click Apply. Change the button’s text property to read “Send Message” and extract the string to a new resource named send_message. Finally, configure the onClick property to call a method named sendMessage.

Implementing the Remote Bound Service

To implement the remote bound service for this example, add a new class to the project by right-clicking on the package name (located under app -> kotlin+java) within the Project tool window and selecting the New -> Service -> Service menu option. Specify RemoteService as the class name and make sure that both the Exported and Enabled options are selected before clicking on Finish to create the class.

The next step is to implement the handler class for the new service. This is achieved by extending the Handler class and implementing the handleMessage() method. This method will be called when a message is received from the client. It will be passed a Message object as an argument containing any data that the client needs to pass to the service. In this instance, this will be a Bundle object containing a string to be displayed to the user. The modified class in the RemoteService.kt file should read as follows once this has been implemented:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

package com.ebookfrenzy.remotebound
 
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.os.Messenger
import android.util.Log
 
class RemoteService : Service() {
 
     class IncomingHandler : Handler(Looper.getMainLooper()) {
 
         val TAG = "RemoteServer"
 
         override fun handleMessage(msg: Message) {
             val data = msg.data
             val dataString = data.getString("MyString")
             Log.i(TAG, "Message = $dataString")
         }
    }
 
    override fun onBind(intent: Intent): IBinder? {
        TODO("Return the communication channel to the service.")
    }
}Code language: Kotlin (kotlin)

With the handler implemented, the only remaining task in terms of the service code is to modify the onBind() method such that it returns an IBinder object containing a Messenger object which, in turn, contains a reference to the handler:

.
.
private val myMessenger = Messenger(IncomingHandler())
 
override fun onBind(intent: Intent): IBinder {
    return myMessenger.binder
}Code language: Kotlin (kotlin)

Android Remote Bound Services – A Worked Example The first line of the above code fragment creates a new instance of our handler class and passes it through to the constructor of a new Messenger object. Within the onBind() method, the getBinder() method of the messenger object is called to return the messenger’s IBinder object.

Configuring a Remote Service in the Manifest File

To accurately portray the communication between a client and remote service, it will be necessary to configure the service to run separately from the rest of the application. This is achieved by adding an android:process property within the <service> tag for the service in the manifest file. To launch a remote service, it is also necessary to provide an intent filter for the service. To implement this change, modify the AndroidManifest.xml file to add the required entry:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ebookfrenzy.remotebound" >
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <service
            android:name=".RemoteService"
            android:enabled="true"
            android:exported="true"
            android:process=":my_process" >
        </service>
 
        <activity
            android:name=".MainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
     </application>
 
</manifest>Code language: HTML, XML (xml)

Launching and Binding to the Remote Service

As with a local bound service, the client component needs to implement an instance of the ServiceConnection class with onServiceConnected() and onServiceDisconnected() methods. Also, in common with local services, the onServiceConnected() method will be passed the IBinder object returned by the onBind() method of the remote service, which will be used to send messages to the server handler. In the case of this example, the client is MainActivity, the code for which is located in MainActivity.kt. Load this file and modify it to add the ServiceConnection class and a variable to store a reference to the received Messenger object together with a Boolean flag to indicate whether or not the connection is established:

package com.ebookfrenzy.remotebound
 
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.content.ComponentName
import android.content.ServiceConnection
import android.os.*
import android.view.View
 
class MainActivity : AppCompatActivity() {
 
    var myService: Messenger? = null
    var isBound: Boolean = false
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
 
    private val myConnection = object : ServiceConnection {
        override fun onServiceConnected(
                className: ComponentName,
                service: IBinder) {
            myService = Messenger(service)
            isBound = true
        }
 
        override fun onServiceDisconnected(
                className: ComponentName) {
            myService = null
            isBound = false
        }
    }
}Code language: Kotlin (kotlin)

Next, some code must be added to bind to the remote service. This involves creating an intent that matches the intent filter for the service as declared in the manifest file and then making a call to the bindService() method, providing the intent and a reference to the ServiceConnection instance as arguments. For this example, this code will be implemented in the activity’s onCreate() method:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

.
.
import android.content.Context
import android.content.Intent
.
.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
 
    val intent = Intent(applicationContext, RemoteService::class.java)    
    bindService(intent, myConnection, Context.BIND_AUTO_CREATE)
}Code language: Kotlin (kotlin)

Sending a Message to the Remote Service

Before testing the application, all that remains is to implement the sendMessage() method in the MainActivity class, which is configured to be called when the user touches the button in the user interface. This method needs to check that the service is connected, create a bundle object containing the string to be displayed by the server, add it to a Message object, and send it to the server:

fun sendMessage(view: View) {
 
    if (!isBound) return
 
    val msg = Message.obtain()
 
    val bundle = Bundle()
    bundle.putString("MyString", "Message Received")
 
    msg.data = bundle
 
    try {
        myService?.send(msg)
    } catch (e: RemoteException) {
        e.printStackTrace()
    }
}Code language: Kotlin (kotlin)

With the code changes complete, compile and run the application. Once loaded, open the Logcat tool window and enter the following into the filter field:

package:mine tag:RemoteServerCode language: plaintext (plaintext)

With the Logcat tool window still visible, tap the button in the user interface, at which point the log message should appear as follows:

Message = Message ReceivedCode language: plaintext (plaintext)

Summary

To implement interaction between a client and remote bound service, an app must implement a handler/ message communication framework. The basic concepts behind this technique have been covered in this chapter, together with the implementation of an example application designed to demonstrate communication between a client and a bound service, each running in a separate process.