In Android development, particularly within the AOSP (Android Open Source Project), managing concurrency is critical when designing reliable and thread-safe components. Two core techniques often used for this are:
- Handler (and its related Looper mechanism)
- The synchronized keyword (Java-level mutual exclusion)
While both can help manage shared state across threads, they serve very different purposes. Understanding when to use each can be the difference between clean concurrency and subtle deadlocks.
1. What is a Handler?
A Handler in Android is a mechanism to post messages or runnables to a thread’s message queue. When backed by a Looper, a Handler provides asynchronous task scheduling on the thread it’s associated with — typically the main/UI thread or a dedicated background thread.
Handler handler = new Handler(Looper.getMainLooper());
handler.post(() -> {
// Run code on the main thread
});
In AOSP system components (like BluetoothManagerService or ActivityManagerService), Handlers are frequently bound to a dedicated thread (HandlerThread) to serialize operations.
2. What is synchronized?
The synchronized keyword is a Java primitive that provides mutual exclusion, ensuring that only one thread can enter a critical section of code at a time.
synchronized(lockObject) {
// Critical section
}
It’s low-level, blocking, and does not switch threads or schedule operations. It simply protects data from concurrent access issues.
3. When to Use Handler in AOSP?
Use Handler when you want to serialize work on a specific thread. For example, if you're managing Bluetooth connections from a central thread:
private final Object mLock = new Object();
void addClient(String id) {
synchronized (mLock) {
mClientIds.add(id);
}
}
This avoids needing synchronized at all, as long as all access to mConnectionMap goes through this handler.
4. When to Use synchronized?
Use synchronized when the access to shared data is sporadic or does not justify a dedicated thread.
private final Object mLock = new Object();
void addClient(String id) {
synchronized (mLock) {
mClientIds.add(id);
}
}
However, avoid long operations inside a synchronized block, especially those that can trigger callbacks or I/O.
5. Best Practices
🧵 Prefer Handlers for Thread Affinity
If your logic has a natural affinity to a thread (e.g., UI or Bluetooth handler thread), use Handler. It allows non-blocking, serialized execution.
⚠️ Use synchronized for Fine-Grained Locks
If data is accessed from multiple threads and there's no dedicated handler thread, then synchronized is still appropriate — but limit scope.
☠️ Avoid Mixing Carelessly
A common pitfall is combining synchronized and Handler-based designs, which can lead to deadlocks if not carefully ordered.
6. Real Example in AOSP
In AdapterService.java (Bluetooth stack), you’ll often find:
mHandler.post(() -> handleConnectionStateChange(device, state));
Internally, handleConnectionStateChange() may access shared maps or state without synchronization — because the handler ensures thread confinement.
In contrast, parts of BatteryService or InputManagerService use synchronized to guard against race conditions due to multiple calling threads.
Conclusion
Both Handler and synchronized are powerful concurrency tools in AOSP. Use Handler when working with components tied to thread-specific behavior, especially when serial execution is required. Use synchronized for fine-grained mutual exclusion without needing message queuing overhead.
Understanding and applying them properly results in safer, cleaner, and more maintainable Android system code.
Top comments (0)