Android Component Initialization and Cross-Thread Communication Architecture
When a user interacts with an application icon, the Android system initiates a complex sequence of inter-process communications. The Launcher application dispatches a start request to the ActivityManagerService (AMS) residing within the system_server process through Binder transactions. Upon receiving this request, AMS evaluates whether a new process instantiation is necessary. If affirmative, it dispatches a process creation command to the Zygote daemon via a Unix domain socket. Zygote performs a fork operation to spawn the target application process, which subsequently registers itself with AMS through an attachApplication IPC call. AMS then proxies lifecycle commands through ApplicationThreadProxy, delivering a scheduleLaunchActivity directive to the newly formed process. The ApplicationThread, operating as a Binder thread within the application, translates this into a LAUNCH_ACTIVITY message dispatched to the main thread's Handler. The ActivityThread orchestrates instantiation through reflection, ultimately invoking the target Activity's onCreate callback.
Activity instances follow specific stacking behaviors defined by four launch modalities:
Standard mode generates a fresh instance for every invocation, creating distinct entries in the task stack. SingleTop optimizes for top-of-stack scenarios—when the target Activity occupies the stack apex, the system routes new intents through onNewIntent rather than instantiating duplicates; otherwise, it creates new instances. SingleTask enforces task affinity constraints—if the Activity exists within its associated task, the system evacuates all activities above it and delivers the intent via onNewIntent. SingleInstance isolates activities by assigning exclusive task stacks, ensuring no other activities coexist in that task container.
The framework monitors execution responsiveness through ANR (Application Not Responding) detection mechanisms. Service components trigger ANRs when foreground operations exceed 20 seconds or background operations extend beyond 200 seconds. Broadcast receivers face tighter constraints: 10 seconds for foreground dispatches and 60 seconds for background processing. ContentProvider publication must complete within 10 seconds to avoid triggering the watchdog.
Service components offer dual initialization paradigms. The command-based approach via startService invokes onCreate followed by onStartCommand, with subsequent invocations bypassing onCreate and routing directly to onStartCommand. Termination requires explicit stopService calls, maintaining independence from component binding states. Alternatively, bindService establishes connections through onCreate, onBind, onUnbind, and onDestroy sequences. Multiple binding requests from connected clients do not re-trigger onBind. Unbinding occurs explicitly through unbindService or implicitly when binding components destroy, cascading to service termination when no active bindings remain.
Service instantiation mirrors Activity startup mechanics. The Application Manager Proxy (AMP) transmits start requests to AMS via Binder. For remote services, AMS coordinates with Zygote to fork dedicated processes. The spawned process establishes its ApplicationThread connection with AMS, which subsequently dispatches scheduleCreateService commands. The receiving ApplicationThread marshals these into CREATE_SERVICE messages for the main thread's Handler, culminating in reflective service instantiation and onCreate invocation. Local services or existing processes bypass the Zygote forking stage.
The Handler framework facilitates thread-to-thread communication through a producer-consumer model. Worker threads must explicitly invoke Looper.prepare() to iintialize message processing infrastructure before Handler construction, as the constructor validates Looper presence through ThreadLocal inspection.
Thread affinity operates through Looper association. When instantiating Handlers on the main thread, the constructor captures the main Looper instance. Subsequent message posts from background threads enqueue into the main Looper's MessageQueue. The Looper's infinite loop retrieves messages and dispatches them to the associated Handler's callback, effectively marshaling execution back to the Loopre's thread.
Implementation requires specific initialization sequences:
public class WorkerThread extends Thread {
private Handler taskHandler;
@Override
public void run() {
Looper.prepare();
taskHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
processTask(msg.obj);
}
};
Looper.loop();
}
private void processTask(Object payload) {
// Task execution logic
}
public Handler getTaskHandler() {
return taskHandler;
}
}
Message objects utilize an object pool pattern through Message.obtain(), recycling instances to minimize garbage collection pressure. The MessageQueue implements a singly-linked list structure ordered by execution timestamps. Each ThreadLocal association permits exactly one Looper instance per thread, enforced through prepare() validation. The main thread initializes its Looper through prepareMainLooper(), creating a non-quittable message queue that persists for the application's lifetime.