In Linux, there are many IPC methods to exchange information between processes. Android binder is one of them, which enables RPC (Remote procedure call). RPC allow client process call method on server process.
1. What is Binder?
Binder is an IPC mechanism that allows processes to send and receive information in a controlled, secure manner.
Elements of Binder:
+ Binder driver: a character driver in kernel that forwards messages between processes.
+ Service manager: a repo for service registry, service lookup and a broker
+ Client and Server: processes that need to communicate.
2. Why Binder?
+ None of other IPCs can provide a common directory where all processes can create their IO file.
+ Binder can provide RPC
+ Binder can support Data marshalling
+ Binder can support Service location, where you can lookup service handler.
3. How binder works?
+ AIDL file: We use AIDL file to generate
// IMyService.aidl
package com.example.myservice;
interface IMyService {
String getData(int id);
}
After generating using AIDL tool, we have following files
IMyService.h: Declares the interface and transaction IDs.
BnMyService.h: Declares the native (server-side) implementation.
BpMyService.h: Declares the proxy (client-side) implementation.
IMyService.h + BnMyService.h
#ifndef AIDL_GENERATED_COM_EXAMPLE_MYSERVICE_IMYSERVICE_H_
#define AIDL_GENERATED_COM_EXAMPLE_MYSERVICE_IMYSERVICE_H_
#include <binder/IInterface.h>
#include <binder/Parcel.h>
#include <utils/String16.h>
namespace com {
namespace example {
namespace myservice {
class IMyService : public android::IInterface {
public:
DECLARE_META_INTERFACE(MyService);
virtual android::String16 getData(int32_t id) = 0;
enum {
GET_DATA_TRANSACTION = android::IBinder::FIRST_CALL_TRANSACTION,
};// use to define the transaction code with the interface
};
} // namespace myservice
} // namespace example
} // namespace com
#endif // AIDL_GENERATED_COM_EXAMPLE_MYSERVICE_IMYSERVICE_H_
class BnMyService : public android::BnInterface<IMyService> {
public:
virtual android::status_t onTransact(uint32_t code,
const android::Parcel& data,
android::Parcel* reply,
uint32_t flags = 0);
};
//onTransact gets called when another process calls Binder object
//code: transaction code that identifies the method client wants to invoke.
//data: input sent by client
//flag: optional
BnMyService.cpp
#include "BnMyService.h"
namespace com {
namespace example {
namespace myservice {
android::status_t BnMyService::onTransact(uint32_t code,
const android::Parcel& data,
android::Parcel* reply,
uint32_t flags) {
switch (code) {
case GET_DATA_TRANSACTION: {
CHECK_INTERFACE(IMyService, data, reply);
int32_t id = data.readInt32();
android::String16 result = getData(id);// write result from function in Bp file
reply->writeString16(result);
return android::NO_ERROR;
}
default:
return android::BBinder::onTransact(code, data, reply, flags);//on server(stub) side, get called when proxy call transact function
}
}
} // namespace myservice
} // namespace example
} // namespace com
The CHECK_INTERFACE macro serves the following purposes:
Validation: It validates that the Parcel object has the correct interface descriptor. This is crucial to ensure that the data being processed corresponds to the expected interface.
Security: It helps prevent incorrect or unauthorized access by ensuring that the Parcel belongs to the expected interface.
Debugging: It assists in debugging by providing a clear checkpoint where an incorrect interface can be detected early in the transaction processing.
BpMyService
#ifndef AIDL_GENERATED_COM_EXAMPLE_MYSERVICE_BPMYSERVICE_H_
#define AIDL_GENERATED_COM_EXAMPLE_MYSERVICE_BPMYSERVICE_H_
#include <binder/Parcel.h>
#include "IMyService.h"
namespace com {
namespace example {
namespace myservice {
class BpMyService : public android::BpInterface<IMyService> {
public:
explicit BpMyService(const android::sp<android::IBinder>& impl);
virtual android::String16 getData(int32_t id);
};
} // namespace myservice
} // namespace example
} // namespace com
#endif // AIDL_GENERATED_COM_EXAMPLE_MYSERVICE_BPMYSERVICE_H_
BpMyService::BpMyService(const android::sp<android::IBinder>& impl)
: android::BpInterface<IMyService>(impl) {}
android::String16 BpMyService::getData(int32_t id) {
android::Parcel data, reply;
data.writeInterfaceToken(IMyService::getInterfaceDescriptor());
data.writeInt32(id);
remote()->transact(GET_DATA_TRANSACTION, data, &reply);//send data to remote via binder driver in kernel
//this is proxy side
return reply.readString16();
}
Create MyService class to implement the function
// MyService.h
#include <binder/BinderService.h>
#include "IMyService.h"
using namespace android;
class MyService : public BinderService<MyService>, public BnMyService {
public:
static char const* getServiceName() { return "MyService"; }
virtual String16 getData(int32_t id) {
// Example implementation
String8 result = String8::format("Data for ID: %d", id);
return String16(result);
}
};
main function to start is like this
// main_b.cpp (ProcessB)
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include "MyService.h"
using namespace android;
int main(int argc, char** argv) {
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
MyService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
return 0;
}
Client logic
// Client.cpp (ProcessA)
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <binder/IPCThreadState.h>
#include "IMyService.h"
using namespace android;
int main(int argc, char** argv) {
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16("MyService"));
if (binder == nullptr) {
ALOGE("Service not found!");
return -1;
}
sp<IMyService> service = interface_cast<IMyService>(binder);
if (service != nullptr) {
String16 result = service->getData(123);
String8 result8(result);
ALOGI("Received: %s", result8.string());
} else {
ALOGE("Failed to cast binder to IMyService interface.");
}
return 0;
}
Diagram that how message is processed
In embedded systems with lots of modules, we totally can utilize binder to transmit messages. It is complicated to implement but quite stable and reliable.
if you like my ideas, hit button 💙
If you have any feedback, make sure to 💬 comment.
If you find this helpful, let’s 🔁 share it.