Why Core Services Avoid Raw Binder
When you build core system services like SurfaceFlinger or AudioFlinger, you need the raw execution speed of C++. Android relies on Binder to let applications communicate with these native services. Writing raw Binder transactions in C++ requires you to manually pack and unpack byte arrays. This manual parceling is tedious, brittle, and notoriously difficult to debug. A single mismatched byte between the client and the server will instantly crash the process. The platform needed a way to guarantee type safety across process boundaries without sacrificing C++ execution speed.
The Android Interface Definition Language (AIDL) compiler solves this by generating the IPC boilerplate for you. While many engineers associate AIDL exclusively with Java, the compiler natively supports C++ targets. You define the interface once in a single .aidl file. The build system then produces the necessary C++ headers, stubs, and proxies automatically. Your custom C++ code interacts with strongly typed methods, while the generated code handles the raw Binder IPC behind the scenes.
Here is how the AIDL compiler translates your interface into native C++ components. This visualizes the exact boundary between the generated code and your custom logic. Look for how the proxy and stub handle the parceling on opposite sides of the IPC divide.
Generated C++ files follow a strict naming convention. The I<InterfaceName> file provides the abstract base class shared by both sides. Your server implements the Bn<InterfaceName> stub, which stands for Binder Native, to receive incoming calls. Clients use the Bp<InterfaceName> proxy, standing for Binder Proxy, to send requests out to the server. You only ever write the implementation logic.
Compiling and Registering the Native Service
Exposing a native service starts with the build system configuration. Modern Android uses the cc_binary or cc_library rules to automate the native AIDL generation. You simply list the .aidl file directly in your source array. The build system detects the extension and invokes the compiler before linking.
// Android.bp
cc_binary {
name: "my_native_service",
srcs: [
"IMyNativeService.aidl",
"MyNativeService.cpp",
"main.cpp",
],
shared_libs: [
"libbinder",
"libutils",
],
}
Common Mistake: Forgetting to include
libbinderin your shared libraries array will cause cryptic linker errors regarding missing parceling functions.
With the build rule in place, you inherit from the generated Bn class to implement the actual logic. Method signatures will precisely match your AIDL definitions.
// MyNativeService.cpp
#include <IMyNativeService.h>
class MyNativeService : public android::BnMyNativeService {
public:
android::binder::Status doSomething(int32_t val) override {
ALOGI("Received %d", val);
return android::binder::Status::ok();
}
};
To make this service discoverable, you must register it with the operating system. Inside the main function of your daemon, you initialize the Binder thread pool and publish your implementation to ServiceManager. Your process then joins the thread pool to listen for incoming requests. This keeps the daemon alive and responsive to IPC calls indefinitely.
// main.cpp
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include "MyNativeService.h"
int main(int argc, char** argv) {
android::sp<android::ProcessState> ps = android::ProcessState::self();
ps->startThreadPool();
android::sp<MyNativeService> svc = new MyNativeService();
android::defaultServiceManager()->addService(
android::String16("my_native_service"),
svc
);
android::IPCThreadState::self()->joinThreadPool();
return 0;
}
Connecting the Client Process
Once the service is running, clients need a reliable way to talk to it. Native clients query ServiceManager to retrieve a raw Binder reference. They then use interface_cast to wrap that raw reference into the strongly typed proxy object.
Here is the exact sequence of how a native client discovers and interacts with your service. This illustrates the handoff from ServiceManager to your custom implementation. Pay attention to how interface_cast transforms the raw token into a usable proxy.
// Client.cpp
android::sp<android::IBinder> binder =
android::defaultServiceManager()->getService(android::String16("my_native_service"));
android::sp<IMyNativeService> proxy =
android::interface_cast<IMyNativeService>(binder);
proxy->doSomething(42);
This architecture forms the foundation of Android internals. The platform uses this design to let a Java application call a hardware-backed C++ daemon safely and efficiently. By relying on native AIDL, the system guarantees cross-language type safety while maintaining the raw performance required by core system services. How these interfaces handle complex data structures across platform updates is exactly where Binder reveals its true flexibility.