Master Android IPC: Services, AIDL, Intents, Broadcasts, Messenger, FileProvider
This guide explores Android's inter‑process communication options—running services in separate processes, defining AIDL interfaces, using explicit and implicit Intents, BroadcastReceiver, Messenger, FileProvider, and ContentProvider—detailing configuration, code examples, and when to choose each method for efficient, secure component interaction.
Effective communication between Android components or apps often requires careful management of inter‑process communication (IPC).
1. Running a Service in an Independent Process
By default all components run in the same process, but you can specify android:process in the manifest to run a Service (or other component) in a separate Linux process.
<service
android:name=".MyService"
android:process=":remote"
android:exported="true"/> android:process=":remote"creates a private process for the app. android:process="com.example.remote" creates a global process that can be shared by multiple apps with the same signature.
Actual Impact
If the client process terminates or crashes, the Service in its own process continues to run until explicitly stopped by Android.
Communication becomes cross‑process and must use Android's IPC mechanisms; you cannot cast the IBinder to a local interface because the object resides in a different memory space.
The android:exported="true" attribute allows other apps to access the Service; setting it to false restricts access to the same app.
2. Using AIDL (Android Interface Definition Language)
AIDL provides a structured, type‑safe IPC mechanism for defining interfaces that can pass primitive types, lists, and custom Parcelable objects.
Define AIDL Interface
interface IMyService {
int calculateSum(int a, int b);
}Implement AIDL Service
class MyAidlService : Service() {
private val binder = object : IMyService.Stub() {
override fun calculateSum(a: Int, b: Int): Int = a + b
}
override fun onBind(intent: Intent): IBinder = binder
}Client Binding
val intent = Intent().apply {
component = ComponentName("com.example", "com.example.MyAidlService")
}
bindService(intent, object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val myService = IMyService.Stub.asInterface(service)
val result = myService.calculateSum(5, 10)
}
override fun onServiceDisconnected(name: ComponentName) {}
}, Context.BIND_AUTO_CREATE)Note: AIDL methods run in the Binder thread pool; long‑running work should be moved to a background thread or coroutine to keep the service responsive.
3. Using Intent for Simple IPC
For small data exchanges, the simplest way is to use an Intent.
Explicit Intent (direct target)
val intent = Intent().apply {
component = ComponentName("com.example.receiver", "com.example.receiver.TargetActivity")
putExtra("key", "value")
}
startActivity(intent)Implicit Intent (action‑based)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Hello from sender!")
}
startActivity(Intent.createChooser(intent, "Choose app"))Receiver manifest declaration:
<activity android:name=".ReceiveActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>4. BroadcastReceiver for “Fire‑and‑Forget” Communication
Broadcasts are suitable for sending simple asynchronous messages to multiple receivers.
Sender
val intent = Intent("com.example.CUSTOM_ACTION").apply {
putExtra("key", "value")
}
sendBroadcast(intent)Receiver declaration in manifest
<receiver android:name=".MyReceiver" android:exported="true">
<intent-filter>
<action android:name="com.example.CUSTOM_ACTION"/>
</intent-filter>
</receiver>Receiver implementation
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val data = intent.getStringExtra("key")
}
}5. Messenger for IPC
Messenger uses a Handler and a message queue for simple request‑response scenarios; it is easier than AIDL but less flexible.
Service side
class MessengerService : Service() {
private val messenger = Messenger(Handler(Looper.getMainLooper()) { msg ->
if (msg.what == 1) Log.d("MessengerService", "Received a message!")
true
})
override fun onBind(intent: Intent): IBinder = messenger.binder
}Client side
val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val messenger = Messenger(service)
val msg = Message.obtain(null, 1)
messenger.send(msg)
}
override fun onServiceDisconnected(name: ComponentName) {}
}
bindService(Intent(this, MessengerService::class.java), connection, BIND_AUTO_CREATE)6. FileProvider for Secure File Sharing
Because of Android’s sandbox, files must be shared via a content:// URI using FileProvider.
Sandbox limitation: apps cannot directly access each other’s file systems.
API compatibility: avoids FileUriExposedException on Android 7.0+.
Fine‑grained permission: shares files without requiring global storage permissions.
Declare Provider in manifest
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.contentprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>Share a file via Intent
val file = File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), "photo.jpg")
val uri = FileProvider.getUriForFile(context, "com.example.contentprovider", file)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "image/*"
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, "Share image"))7. ContentProvider for Structured Data Access
ContentProvider enables cross‑process or cross‑app structured data queries, similar to a database table.
Declare Provider in manifest
<provider
android:name=".MyProvider"
android:authorities="com.example.myprovider"
android:exported="true"
android:grantUriPermissions="true"/>Provider implementation
class MyProvider : ContentProvider() {
override fun query(
uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
// Return cursor data
return null
}
// Implement insert(), update(), delete(), getType() as needed
}Client query
val uri = Uri.parse("content://com.example.myprovider/items")
val cursor = contentResolver.query(uri, null, null, null, null)
cursor?.use {
val data = it.getString(it.getColumnIndex("column_name"))
// Process data
}Conclusion: Choose the Right IPC Mechanism
When selecting an IPC mechanism, consider data volume, complexity, and communication pattern:
Intent: best for minimal data exchange or component navigation.
Broadcast: suitable for fire‑and‑forget notifications to multiple receivers.
Messenger: middle‑ground request‑response using Handler/Message; easy for simple cross‑process control.
AIDL: heavyweight, type‑safe solution for rich interfaces, lists, custom Parcelables, and concurrent access.
FileProvider: preferred for securely sharing large files or documents via content:// URIs.
ContentProvider: designed for structured data access across apps, offering a consistent permission model.
Choosing the appropriate IPC method ensures efficient, secure, and maintainable communication between Android processes and apps.
AndroidPub
Senior Android Developer & Interviewer, regularly sharing original tech articles, learning resources, and practical interview guides. Welcome to follow and contribute!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
