IPC: A buzz word in Modern Android Development Paradigm
Inter-process communication (IPC) in Android is how different apps and system components talk to each other safely, even though each runs in its own isolated Linux process. Android achieves this with a kernel driver called Binder, wrapped in familiar APIs like Intents, Services, ContentProviders, AIDL, and Messenger.
1. Story first: apartments and the intercom
Imagine every Android app as a separate apartment in a high-security building.
Each apartment (process) has:
- Its own space (memory).
- Its own keys (UID/permissions).
- No direct access to other apartments.
Yet, apps need to talk:
- Food-delivery app → asks Google Maps for routes.
- Camera app → sends photos to the gallery.
- Music app → asks the system if it can play over an ongoing call.
Whenever this conversation crosses apartment boundaries (processes), you are doing IPC. Binder is the building’s intercom system that connects apartments and the building’s control room (system services).
You already use IPC whenever you:
- Start another Activity via
Intent. - Use
ContentResolverto query contacts or media. - Call system services like
LocationManager,NotificationManager,MediaSessionManager.
You were doing IPC all along; the goal of this article is to make that visible and designable.
2. Three-layer mental model of IPC
To understand IPC like an advanced Android dev, think in three layers.
2.1 Conceptual layer: OS rules
- Each process has its own address space; you cannot directly read another process’s memory.
- Any communication must cross a boundary with explicit data copying and permission checks.
This is what gives Android its sandboxing and crash isolation.
2.2 Binder layer: the intercom in the kernel
Binder is a kernel driver plus user-space support that behaves like a compact, optimized RPC system:
- A server exposes a Binder object (the real implementation).
- A client holds a proxy object with the same interface.
- Method calls on the proxy are marshalled into
Parcels, sent through the Binder driver, and dispatched to the server’s implementation.
Key properties:
- Marshalling: Only supported types (primitives,
String,Parcelable,Bundle, file descriptors) can cross. - Threading: Incoming Binder calls are handled by a Binder thread pool in the server process.
- Security: Every call carries caller UID/PID; services can enforce permissions at the IPC boundary.
2.3 Framework layer: APIs you actually use
Almost everything you touch for IPC is built on top of Binder:
- Intents & Services
- ContentProviders
- AIDL-based interfaces
- Messenger
This article lives mostly at this layer but keeps linking back to Binder to build intuition.
3. Intents & Services: postcards and tasks
3.1 Intents = postcards between apps
Analogy: an Intent is a postcard you send to another component.
- What: action like
ACTION_VIEW,ACTION_SEND. - To whom: explicit component or implicit (via intent-filters).
- Extra info: data URI and extras.
Example: send text to any “share” target
val sendText = "Check out this article about IPC in Android!"
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, sendText)
}
startActivity(Intent.createChooser(intent, "Share via"))
When you call startActivity, startService, or send a broadcast:
- You drop the postcard at the “front desk” (ActivityManager, etc.).
- The system finds a matching receiver and delivers the message — often into another process — over Binder.
For most app navigation and one-off commands, Intents are the right IPC tool.
3.2 Bound services = long-running conversations
When you bindService, you are saying:
“Keep a connection open; I want to talk repeatedly with this component.”
Manifest: service in a separate process
<service
android:name=".RemoteCounterService"
android:process=":remote" />
Service: simple Binder in onBind
class RemoteCounterService : Service() {
private var counter = 0
// Simple Binder implementation (same process or cross-process)
private val binder = object : Binder() {
fun increment(): Int {
counter++
return counter
}
}
override fun onBind(intent: Intent): IBinder = binder
}
Client: bind and call (if remote, this is IPC)
class CounterActivity : AppCompatActivity() {
private var serviceBinder: Binder? = null
private var bound = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
serviceBinder = service as Binder
bound = true
// This call is IPC if service is in another process.
val result = serviceBinder?.let { b ->
// In real code you'd expose a typed API instead of casting like this.
(b as? RemoteCounterServiceBinder)?.increment()
}
}
override fun onServiceDisconnected(name: ComponentName) {
bound = false
serviceBinder = null
}
}
override fun onStart() {
super.onStart()
Intent(this, RemoteCounterService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
if (bound) {
unbindService(connection)
bound = false
}
}
}
- The client gets an
IBinderinonServiceConnected. - If the service runs in a different process (
android:processin manifest), every method call on that binder is IPC.
Use cases:
- Media playback engines.
- Analytics or sync engines shared by multiple modules.
- SDK-style services.
The binder you receive might be:
- A simple custom
Bindersubclass. - A generated AIDL Stub.
- A
Messenger-wrapped Binder.
4. ContentProvider: the shared library with a librarian
A ContentProvider is Android’s structured data-sharing mechanism.
Analogy: a shared library:
ContentResolveris your library card.- The URI (e.g.,
content://contacts/…) is the shelf address. query,insert,update,deleteare your library operations.
Every such call:
- Goes from your process to the provider’s process through Binder.
- Enforces permissions based on URI, calling UID, and declared authorities.
Use it when:
- Multiple apps or modules must access the same structured data with a stable schema.
- You want fine-grained permission control per table/row.
4.1 Client-side query example
val projection = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
)
val cursor = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
projection,
null, // selection
null, // selectionArgs
"${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} ASC"
)
cursor?.use {
val nameIndex = it.getColumnIndexOrThrow(
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
)
while (it.moveToNext()) {
val name = it.getString(nameIndex)
// Use name...
}
}
This looks like a local call, but internally:
- Your process calls a system service over Binder.
- That service forwards to the ContactsProvider in another process via Binder.
4.2 Provider-side minimal implementation
class NotesProvider : ContentProvider() {
override fun onCreate(): Boolean {
// Initialize your DB / repository.
return true
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
// Use Uri + selection to build query.
// Return Cursor backed by your DB.
// This Cursor will be consumed in another process via IPC.
return notesDb.queryNotes(projection, selection, selectionArgs, sortOrder)
}
// Implement insert/update/delete/getType...
}
The system turns a client query() into a Binder transaction to this provider instance.
5. AIDL: shared utility room with a contract
5.1 Story: shared utility room
Imagine a shared utility room in the building with heavy machinery (e.g., a central media engine).
Multiple apartments (apps) need to:
- Start/stop it.
- Query its state.
- Receive callbacks (e.g., playback progress).
You need a precise contract describing which buttons exist and how to use them:
play(Track track)pause()getCurrentPosition(): long
In Android, this contract is written in AIDL.
AIDL interface: IRemoteCalculator.aidl
// IRemoteCalculator.aidl
package com.example.ipc;
// Simple remote calculator API
interface IRemoteCalculator {
int add(int a, int b);
int multiply(int a, int b);
}
The tools generate IRemoteCalculator.Stub (server) and IRemoteCalculator.Stub.Proxy (client).
5.2 How AIDL actually works
- You define an interface in an
.aidlfile. - The build system generates:
- A Stub (server side) that extends
Binderand implements unpacking logic. - A Proxy (client side) that packs calls into
Parcels and does Binder transactions.
Service side:
- Extend the
Stubclass and implement the methods. - Return this stub from
onBindin yourService. - Methods are executed on Binder threads; you must handle thread-safety and heavy work offloading.
Client side:
- Bind to the service.
- In
onServiceConnected, callYourInterface.Stub.asInterface(serviceBinder)to get the proxy. - Call methods as if it were a local interface; behind the scenes, this is synchronous IPC.
class RemoteCalculatorService : Service() {
private val binder = object : IRemoteCalculator.Stub() {
override fun add(a: Int, b: Int): Int {
// IPC call lands here on a Binder thread.
return a + b
}
override fun multiply(a: Int, b: Int): Int {
return a * b
}
}
override fun onBind(intent: Intent?): IBinder = binder
}
Add the service in manifest, optionally in its own process:
<service
android:name=".RemoteCalculatorService"
android:process=":remote"
android:exported="true">
<intent-filter>
<action android:name="com.example.ipc.REMOTE_CALCULATOR" />
</intent-filter>
</service>
Client binding and usage
class CalculatorClientActivity : AppCompatActivity() {
private var remoteCalculator: IRemoteCalculator? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
// Convert raw IBinder into our typed interface (proxy).
remoteCalculator = IRemoteCalculator.Stub.asInterface(service)
// This call is IPC if service is in :remote process.
val sum = remoteCalculator?.add(2, 3)
val product = remoteCalculator?.multiply(4, 5)
}
override fun onServiceDisconnected(name: ComponentName) {
remoteCalculator = null
}
}
override fun onStart() {
super.onStart()
val intent = Intent("com.example.ipc.REMOTE_CALCULATOR").apply {
setPackage("com.example.ipc") // service package
}
bindService(intent, connection, BIND_AUTO_CREATE)
}
override fun onStop() {
super.onStop()
unbindService(connection)
}
}
This is the minimal “remote calculator” example in AIDL form, adapted from official docs and common samples.
5.3 When AIDL is the right choice
AIDL is appropriate when:
- You have a multi-client, multi-process service.
- You need strong typing and parallel calls via Binder’s thread pool.
- The interface is a long-lived API that you treat like an SDK surface (e.g., media, device control, enterprise SDK).
For typical app architectures, AIDL appears when you cross the line from “just an app” into “platform or SDK building.”
6. Messenger: a chat app between processes
6.1 Story: chat instead of methods
Messenger is “IPC as chat messages” instead of typed method calls.
- A service creates a
Handlerand wraps it in aMessenger. - The client gets this Messenger and calls
send(Message). - Messages arrive on the service’s
Handlerthread, one by one.
Each message has:
what→ operation code (e.g.,PLAY,PAUSE).arg1/arg2andBundle→ parameters.
Service with Handler + Messenger
class MessengerService : Service() {
companion object {
const val MSG_SAY_HELLO = 1
}
// Handle incoming messages from clients
private val incomingHandler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO -> {
// Do something when client says hello
Log.d("MessengerService", "Hello from client")
}
else -> super.handleMessage(msg)
}
}
}
// Wrap the Handler in a Messenger
private val messenger = Messenger(incomingHandler)
override fun onBind(intent: Intent?): IBinder = messenger.binder
}
Client sending a message
class MessengerClientActivity : AppCompatActivity() {
private var serviceMessenger: Messenger? = null
private var bound = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
serviceMessenger = Messenger(service)
bound = true
// Send a "hello" message
val msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0)
serviceMessenger?.send(msg)
}
override fun onServiceDisconnected(name: ComponentName) {
bound = false
serviceMessenger = null
}
}
override fun onStart() {
super.onStart()
Intent(this, MessengerService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
if (bound) {
unbindService(connection)
bound = false
}
}
}
Here:
- All messages land on
incomingHandler’s thread, one by one. - This is simpler than AIDL but less typed and not parallel by default.
6.2 When Messenger makes sense
Messenger is good when:
- You have a simple protocol: a few commands, small payloads.
- You want serialized handling (no concurrency headaches) on a single thread.
- You like the
Handler/Messagemodel and don’t need AIDL’s type safety or parallelism.
If you feel you are encoding too many what codes and Bundles, you have outgrown Messenger and should consider AIDL.
7. Design guidelines: choosing the right IPC primitive
Here’s a advance-level heuristic for everyday design decisions.
7.1 Simple rule of thumb
- One-shot, loosely coupled actions
Example: open screen, share data, trigger work.
Use: Intent / Broadcast.
- Shared structured data
Example: contacts, media, documents.
Use: ContentProvider.
- Ongoing conversation, small protocol
Example: few commands, low frequency, single-threaded handling.
Use: Messenger.
- Ongoing conversation, rich API
Example: media engine, device/SDK service, multi-client remote API.
Use: AIDL-bound service.
7.2 API and performance considerations
- Treat IPC interfaces as public APIs: design for evolution (additive changes, avoid breaking method signatures).
- Never block the main thread on IPC; remote calls can stall if the other side is slow or dead.
- On the service side, don’t do heavy work on Binder threads; hand off to your own coroutines/executors.
8. FAQ / Q&A
Q1: Is using startActivity or ContentResolver.query really “IPC”?
Yes — when the target lives in another process, these calls are IPC.
The system converts your API call into a Binder transaction to ActivityManager or the target provider, then into another process.
Q2: Binder vs AIDL vs Messenger — what’s the relationship?
- Binder is the low-level IPC mechanism and base class (
Binder/IBinder). - AIDL is an IDL and code generator that produces Binder-based Stub/Proxy for typed RPC.
- Messenger is a higher-level wrapper that uses Binder under the hood but exposes a message-passing API around
Handler.
Q3: When should I switch from Intents to a bound service?
Switch when:
- You need a long-lived connection.
- You need callbacks or high-frequency interactions (e.g., player controls, data streams).
- You need to model a stateful remote object rather than a one-off event.
Q4: What do in, out, inout mean in AIDL?
They describe direction of data for complex parameters:
in: data only flows from client to service.out: service fills the object and sends it back.inout: object is sent to service, possibly modified, then sent back.
They matter because they affect how many times data is marshalled and thus performance.
Q5: Why does Binder carry UID/PID, and how is it used?
Each Binder call carries caller UID/PID, letting the callee enforce security:
- System services can check
checkCallingPermissionto ensure the caller has declared the appropriate permission. - This is how manifest permissions like
ACCESS_FINE_LOCATIONare enforced at runtime by location or other managers.
Q6: Why not just use sockets or shared memory instead of Binder?
You can use them at low level, but Binder adds:
- Built-in identity and permission integration.
- Object reference management and lifecycle tracking.
- A consistent, optimized RPC model used across the framework.
For most app and platform components, this consistency beats rolling custom socket protocols.
If you have reached till here, hoping you found this blog useful 🙌🏻. Kindly share your valuable feedback/suggestions with me on below links.
EmailId: vikasacsoni9211@gmail.com
LinkedIn: https://www.linkedin.com/in/vikas-soni-052013160/
Happy Learning ❤️

Comments
Post a Comment