Android Service Activity Communication (5 Ways)

Android Service Activity Communication

In this article, we’ll learn different methods to establish two-way communication between the Service and Activity in Android.

Prerequisites:

These are the methods we will explore:

  1. Create Static Variables and Methods
  2. Create a Bound Service
  3. Use Intent to Send Data to Service
  4. Create Singleton Instance of the Service
  5. Use Broadcast Receiver

1. Create Static Variables and Methods:

This is the simplest method. Make the variables (you want to share between the Activity and Service) static. Both the Activity and Service can access and update the variables at the same time.

Example:

Create a static variable in your Service class.

class MyService : Service() {

    companion object {
        var count = 0
    }

    override fun onCreate() {
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // update the value
        count = 100

        return START_STICKY
    }

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

We have created a count variable. As it is static, we can easily access it in the Activity.

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
      
        // access the variable
        Log.d("MainActivity", "count = ${MyService.count}")

        // update the variable
        MyService.count = 500
    }
}

2. Create a Bound Service (Best for Two-way Communication):

A bound service is a server in a client-server interface. It allows activities to bind to the service to send and receive the data. It typically lives only while it servers the components (like activity) and doesn’t run in the background indefinitely.

Let’s create a bound service. First, open your service class and create an inner class that extends Binder().

class MyService : Service() {

    inner class MyBinder : Binder() {
        fun getService(): MyService = [email protected]
    }
}

We have created getService() method in the MyBinder class. It returns the service instance. Clients (like activities) can use it to access the variables and methods of the MyService().

Next, override onBind() method and return the instance of MyBinder().

class MyService : Service() {

    private val binder = MyBinder()

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

    inner class MyBinder : Binder() {
        fun getService(): MyService = [email protected]
    }
}

Next, create a variable called randomNumber in the Service. Change its value for every 5 seconds.

class MyService : Service() {

    var randomNumber = 0

    private val binder = MyBinder()

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

    override fun onCreate() {
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        thread {
            while (true) {
                randomNumber = (1..100).random()
                Log.d("MyService", "Generated randomNumber = $randomNumber")
                Thread.sleep(5000)
            }
        }
        return START_STICKY
    }

    inner class MyBinder : Binder() {
        fun getService(): MyService = [email protected]
    }
}

In the Activity, create two variables – mService and mBound. mService is the service instance and mBound is the boolean variable that shows if the service is bounded or not.

class MyActivity : AppCompatActivity() {

    private lateinit var mService: MyService
    private var mBound: Boolean = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
    }
}

We can bind the Activity to the Service by calling bindService() method.

public abstract boolean bindService (Intent service, ServiceConnection conn, int flags)

It takes 3 parameters.

service – It is an Intent object.

conn – It is a ServiceConnection object.

flags – It indicates the options for binding. For this article, we are using BIND_AUTO_CREATE. You can find the list of all the flags here.

Let’s create the ServiceConnection object. It is an interface with two methods:

1. onServiceConnected() – called when the connection to the service is established.

2. onServiceDisconnected() – called when the connection to the service has been lost.

class MyActivity : AppCompatActivity() {

    private lateinit var mService: MyService
    private var mBound: Boolean = false
    
    private val connection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // when the service is connected, get its instance
            val binder = service as MyService.MyBinder
            mService = binder.getService()
            mBound = true
        }

        override fun onServiceDisconnected(arg0: ComponentName) {
            // service disconnected
            mBound = false
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
    }
}

Now, override onStart() and onStop() methods in the Activity and call bindService() and unbindService() methods accordingly.

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
    }

    override fun onStart() {
        super.onStart()
        // Bind to MyService
        val intentBind = Intent(this, MyService::class.java)
        bindService(intentBind, connection, Context.BIND_AUTO_CREATE)
    }

    override fun onStop() {
        super.onStop()
        // Disconnect MyService
        unbindService(connection)
        mBound = false
    }
}

Now, we can easily access the randomNumber using the mService object. Create a button in the Activity and get its value in the on-click listener.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        val button = findViewById<Button>(R.id.button)

        button.setOnClickListener {
            if (mBound) {
                val number = mService.randomNumber
                Log.d("MainActivity", "randomNumber = $number")
            } else {
                Log.d("MainActivity", "Service not bound")
            }
        }
 }

Now, run the app. Don’t forget to start the service using the startService() or startForegroundService() method. When you tap on the button, you will see the randomNumber value in the log cat.

You can also call service methods from Activity. Add the following method to the Service class.

fun printMessage() {
    Log.d("MyService", "Hi! I'm in Service")
}

In the Activity, you can call it using the mService object like this:

mService.printMessage()

Note: This approach only works when the service and activity are running in the same process (which is true for most applications).

3. Use Intent to Send Data to Service:

This is the easiest way to send data to the Service. While calling the startService() method, add data to the intent object.

In the Activity:

val intentService = Intent(this, MyService::class.java)
intentService.putExtra("Number_key", 200)
startService(intentService)

In the Service:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val number = intent?.getIntExtra("Number_key", 0)
    Log.d("MyService", "number = $number")

    return START_STICKY
}

You can call startService() multiple times. If the service is not running, a process is created. If it is already running, it remains running. Every time you call it, the target service’s onStartCommand() is triggered with the given intent. But, it is an expensive operation. It takes some milliseconds to get executed.

4. Create a Singleton Instance of the Service:

We can also create a singleton instance of the service to share data. In your service class, create an object with name sInstance:

class MyService : Service() {

    var randomNumber = 0

    companion object {
        var sInstance: MyService? = null
    }

    override fun onCreate() {
        super.onCreate()
        sInstance = this
    }

    fun printMessage() {
        Log.d("MyService", "Hi! I'm in Service")
    }

    override fun onDestroy() {
        sInstance = null
        super.onDestroy()
    }
}

In the Activity, we can access randomNumber and call printMessage() method like this:

val number = MyService.sInstance?.randomNumber
MyService.sInstance?.printMessage()

Note: You should be careful with this approach because you could get a memory leak. Make sure that you test the app with LeakCanary before publishing your app.

5. Use Broadcast Receiver:

We can use broadcasts to send data to the Service. We send the data by using the sendBroadcast() method. It looks like this:

public abstract void sendBroadcast (Intent intent)

It accepts the Intent object. In the Activity, attach the data to the intent and pass it to the method.

private fun sendDataToService() {
    val intent1 = Intent("my_service_action").putExtra("Number_key", 120)
    sendBroadcast(intent1)
}

In your service class, create a broadcast receiver object and register it in the onCreate() method.

class MyService : Service() {

    private val serviceBroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val number = intent.getIntExtra("Number_key", 0)
            Log.d("MyService", "onReceive number = $number")
        }
    }

    override fun onCreate() {
        super.onCreate()
        // register the receiver
        val intentFilter = IntentFilter("my_service_action")
        registerReceiver(serviceBroadcastReceiver, intentFilter)
    }

    override fun onDestroy() {
        unregisterReceiver(serviceBroadcastReceiver)
        super.onDestroy()
    }
}

Now, run the app. After the service is started and running, call the sendDataToService() method. The onReceive() function of the broadcast receiver is executed and you will see the number value on the log cat.

Note: Broadcast receivers take time (a few milliseconds) to send and receive the data. They are not reliable if you communicate with the service frequently.

These are the different methods to establish communication between the Activity and Service in Android. I hope you have learned something new. If you have any doubts, comment below.

Related:

Leave a Comment