Android Development
Android Service
Activity Communication
Mobile App Development
Java Android

How to have Android Service communicate with Activity

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

An Android Activity and a Service often need to exchange state, progress, or commands, but they live on different lifecycle tracks. The right communication approach depends mostly on whether the activity needs to call the service directly, receive updates from it, or communicate across process boundaries.

For same-app, same-process communication, a bound service is usually the cleanest option. For looser coupling or background notifications, callbacks, broadcasts, or observable shared state can work better.

Bound Service for Direct Communication

A bound service lets the activity call methods on the service directly through a binder. This is the most straightforward pattern when the activity is actively on screen and needs live access to service state.

Service example in Kotlin:

kotlin
1class PlayerService : Service() {
2
3    private val binder = LocalBinder()
4    private var progress: Int = 0
5
6    inner class LocalBinder : Binder() {
7        fun getService(): PlayerService = this@PlayerService
8    }
9
10    override fun onBind(intent: Intent): IBinder {
11        return binder
12    }
13
14    fun getProgress(): Int = progress
15
16    fun startWork() {
17        progress = 42
18    }
19}

Activity binding to the service:

kotlin
1class MainActivity : AppCompatActivity() {
2
3    private var playerService: PlayerService? = null
4    private var bound = false
5
6    private val connection = object : ServiceConnection {
7        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
8            val binder = service as PlayerService.LocalBinder
9            playerService = binder.getService()
10            bound = true
11        }
12
13        override fun onServiceDisconnected(name: ComponentName?) {
14            playerService = null
15            bound = false
16        }
17    }
18
19    override fun onStart() {
20        super.onStart()
21        Intent(this, PlayerService::class.java).also {
22            bindService(it, connection, Context.BIND_AUTO_CREATE)
23        }
24    }
25
26    override fun onStop() {
27        super.onStop()
28        if (bound) {
29            unbindService(connection)
30            bound = false
31        }
32    }
33}

This pattern is simple and efficient when both components are in the same process.

Sending Updates from Service Back to Activity

Direct method calls from activity to service are only half the story. Often the service needs to push updates such as progress or status changes back to the activity.

A callback interface is one option:

kotlin
interface ProgressListener {
    fun onProgressChanged(value: Int)
}

The service can keep a nullable listener reference and notify it when work changes. But a callback must be managed carefully to avoid leaking the activity after configuration changes.

Another option is to expose observable state from a repository or other shared component that both the service and activity use. That reduces direct lifecycle coupling, though it adds architectural complexity.

Messenger or IPC Cases

If communication crosses processes, a local binder is not enough. In that case Android provides patterns such as:

  • 'Messenger for message-based IPC'
  • 'AIDL for more formal cross-process interfaces'

These are more advanced and usually unnecessary unless the service is designed for inter-process communication.

For many ordinary app cases, the question is not "how do I send messages across processes." It is "how do I avoid overcomplicating a same-process app." The answer is usually to stay with a bound service or a shared observable state holder.

Choosing the Right Pattern

Use a bound service when:

  • the activity needs direct access to service methods
  • both components live in the same process
  • the UI is actively interacting with the long-running component

Use looser event-style communication when:

  • multiple screens may observe the same service state
  • the activity may come and go while work continues
  • you want less direct coupling

There is no one universal pattern. The best choice depends on whether the communication is command-driven, state-driven, or cross-process.

Common Pitfalls

  • Forgetting to unbind the service can leak the activity.
  • Trying to update the UI directly from a background thread in the service can crash or behave unpredictably.
  • Keeping a strong callback reference to a dead activity can create lifecycle bugs after rotation.
  • Using IPC-heavy patterns for a same-process app usually adds complexity without benefit.

Summary

  • A bound service is usually the simplest way for an activity to communicate with a service in the same app.
  • The activity can call service methods through a binder after binding.
  • Service-to-activity updates need lifecycle-aware callbacks or a shared observable state approach.
  • Use Messenger or AIDL only when real cross-process communication is required.
  • Pick the communication pattern based on lifecycle needs, not just on what Android technically allows.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.