Android Activity Stacks, Process Lifecycles, and Inter-Process Data Transfer
Task Management and Navigation Stacks
When users interact with an application, they engage with a sequence of screens represented by Activity instances. The system organizes these instances into a task—a cohesive unit that arranges activities in a last-in-first-out (LIFO) structure known as the back stack. Consider an email client: selecting a message pushes a detail view onto the stack, while pressing the back button pops that view and restores the previous list state.
Launching an activity places it at the top of the stack and shifts focus to it, pausing the previous activity but preserving its UI state. Android 7.0+ introduces multi-window support where each window maintains independent task stacks. On desktop environments like Chrome OS, the system manages task groups per window.
Tasks typically originate from the home screen launcher. Tapping an icon brings the associated task to the foreground or creates a new task with the root activity if none exists. When multiple applications run simultaneously, background tasks retain their stack states but lose focus, enabling multitasking scenarios where users switch between distinct application contexts.
Activity Launch Behaviors
The system provides several mechanisms to control how activities instantiate and associate with tasks:
Manifest Declarations
The launchMode attribute determines instantiation behavior:
standard: Default behavior creating new instances per launch request. Multiple instences can exist across different tasks.singleTop: Reuses the existing top-of-stack instance viaonNewIntent()when available, otherwise creates new.singleTask: Maintains a single instance across the entire system. If the instance exists in another task, that task surfaces to foreground.singleInstance: Similar to singleTask but prohibits additional activities from joining its task.
Intent Modifiers
Runtime flags override manifest configurations:
FLAG_ACTIVITY_NEW_TASK: Mirrors singleTask behavior, initiating new tasks or surfacing existing ones.FLAG_ACTIVITY_SINGLE_TOP: Mirrors singleTop behavior.FLAG_ACTIVITY_CLEAR_TOP: Destroys all activities above the target instance, delivering the intent to the resumed target.
Task Affinity and Stack Management
Task affinity defines which task an activity prefers to join. By default, activities within an application share the same affinity. The taskAffinity attribute modifies this association, useful when combining activities from different applications or separating logical modules within one APK.
When allowTaskReparenting is enabled, activities migrate to tasks matching their affinity when those tasks enter the foreground. This proves valuable for reusable components like weather widgets that should appear within their parent application's task rather than the launching app's context.
For stack maintenance, several attribute control persistence:
alwaysRetainTaskState: Prevents automatic clearing of background task stacks.clearTaskOnLaunch: Resets the stack to root upon re-entry.finishOnTaskLaunch: Removes specific activities when users return to tasks.
Application Process Lifecycle
Android applications execute within Linux processes whose lifespans depend on system resource availability and component states. The framework categorizes processes into a hierarchy of importance:
-
Foreground Processes: Currently executing user-visible operations—running activities in resumed state, active broadcast receivers, or service lifecycle callbacks. These survive until memory exhaustion.
-
Visible Processes: Hosting paused activities (visible behind dialogs) or foreground services. These terminate only when necessary to sustain foreground processes.
-
Service Processes: Running services initiated via
startService(), performing background operations like synchronization. Extended execution (30+ minutes) may downgrade priority to cached status. -
Cached Processes: Inactive activities in stopped states, maintained for quick relaunch but terminated first during memory pressure. The system maintains these in a least-recently-used queue.
Process importance elevates when bound to higher-priority components through Context.BIND_AUTO_CREATE or ContentProvider dependencies.
Inter-Process Communication and Data Transfer
For passing information between components, Android utilizes Bundle objects optimized for parceling. When launching activities:
val navigationIntent = Intent(applicationContext, DetailView::class.java).apply {
putExtra("item_identifier", "xyz789")
putExtra("content_type", "article")
}
startActivity(navigationIntent)
For complex objects requiring custom serialization, implement Parcelable:
class MediaMetadata(val id: String, val duration: Long) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readLong()
)
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(id)
dest.writeLong(duration)
}
override fun describeContents(): Int = 0
companion object {
@JvmField
val CREATOR: Parcelable.Creator<MediaMetadata> = object : Parcelable.Creator<MediaMetadata> {
override fun createFromParcel(source: Parcel): MediaMetadata = MediaMetadata(source)
override fun newArray(size: Int): Array<MediaMetadata?> = arrayOfNulls(size)
}
}
}
Critical Constraints
Binder transactions impose a 1MB buffer limit shared across all application processes. Exceeding this threshold triggers TransactionTooLargeException. This constraint affects onSaveInstanceState(), activity launches, and system interactions.
Guidelines for data transfer:
- Keep intent extras under 100KB
- Maintain saved state below 50KB
- Avoid custom Parcelable objects when broadcasting to system components (alarms, notifications), as the framework may strip unrecognized classes during intent modification
- Never persist Parcel data to disk or networks
Android 7.0+ throws runtime exceptions for size violations, while earlier versions log warnings silently.