Understanding Binder's Role in Performance
Binder is the heart of Android's Inter-Process Communication (IPC) mechanism. Every time an application talks to the system server (to start an activity, register a receiver, or show a window), it makes a Binder call. Because these calls cross process boundaries and involve context switches, they inherently introduce latency.
When performance issues arise in AOSP, Binder is often the suspect. Analyzing Binder transactions is crucial for identifying bottlenecks, thread starvation, and deadlocks.
Binder Latency Measurement
A standard synchronous Binder call blocks the calling thread until the remote process finishes executing the request and returns a result. If the remote process is slow or blocked, the calling process is also delayed.
Measuring with Perfetto
Perfetto is the primary tool for visualizing Binder latency. When you enable the binder_driver trace category, Perfetto tracks every transaction across the kernel.
In the ui.perfetto.dev timeline, a Binder call looks like an arrow connecting two processes:
- The Client: The track shows a
binder transactionslice. The length of this slice is the total latency experienced by the caller. - The Server: The arrow points to a
binder replyslice on a thread in the remote process (e.g.,system_server). The length of this slice is how long the server took to process the request.
If the client's slice is long, you must inspect the server's thread. Was the server actively executing code (Running), or was it waiting for a lock (Sleeping)?
Binder Thread Starvation Detection
Every process that uses Binder maintains a pool of threads specifically dedicated to handling incoming Binder requests. By default, Android configures a maximum of 15 Binder threads per process.
The Starvation Problem
If a process receives many concurrent requests, and the threads processing those requests get blocked (e.g., waiting for disk I/O, or waiting for a single heavily contested lock), the entire pool of 15 threads can become exhausted.
When this happens, any new incoming Binder requests will be queued by the kernel. The calling process will be blocked indefinitely, waiting for a thread to become available. This is known as "Binder Thread Starvation" and is a primary cause of system-wide ANRs.
Detecting Starvation in Traces
To detect starvation in a Perfetto trace:
- Locate the target process (usually
system_server). - Look at the threads named
Binder:<pid>_X. - If all 15 threads are stuck in a "Sleeping" or "Blocked" state simultaneously for a long duration, the pool is exhausted.
- The timeline will often show incoming Binder transaction arrows queuing up at the boundary of the process, waiting for one of those threads to wake up.
High Binder Transaction Rate Optimization
Sometimes the issue is not that a single Binder call is slow, but that a process is making too many Binder calls in a short period. This is often referred to as "Binder spam".
Every Binder transaction involves memory copying in the kernel and context switching. Making thousands of calls per second will overwhelm the CPU and cause severe jank.
Identifying High Volume
Using Perfetto's SQL Trace Processor, you can easily identify processes spamming the Binder driver:
SELECT
process.name AS client_process,
COUNT(*) AS transaction_count
FROM slice
JOIN thread_track ON slice.track_id = thread_track.id
JOIN thread USING(utid)
JOIN process USING(upid)
WHERE slice.name = 'binder transaction'
GROUP BY process.name
ORDER BY transaction_count DESC
LIMIT 10;
Optimization Strategies
If you discover a high transaction rate, the solutions usually involve:
- Batching: Instead of making 100 calls to retrieve 100 items, create a new Binder API that retrieves a list of 100 items in a single call.
- Caching: If the data rarely changes, cache it locally in the client process and only use Binder to subscribe to invalidation events.
- Asynchronous Calls: Use
onewayBinder interfaces for fire-and-forget operations. This prevents the client thread from blocking while the server processes the request.
Advanced Analysis: BpBinder and BBinder
When diving into native crashes or complex deadlocks, understanding the native C++ objects involved is helpful.
BpBinder(Binder Proxy): This object lives in the client process. It acts as a local handle to a remote object.BBinder(Binder Base): This object lives in the server process. It is the actual implementation of the service.
If you are analyzing a memory leak involving Binder, you will often look for leaked BpBinder references in the client process, which keep the corresponding BBinder alive in the server process, preventing it from being garbage collected.