Unlike standard desktop applications that possess a single, monolithic entry point (like public static void main), an Android application is built as a loose collection of independent entry points invoked by the Android OS Linux kernel and system server (via Binder IPC). Every component serves a specialized operational contract:
content:// URIs.As users navigate forward, press back, answer phone calls, or switch apps, the Android OS drives the Activity through a deterministic state machine:
onCreate(): Called when the Activity is first instantiated. This is where static initialization occurs: inflating layouts (setContentView), initializing ViewModels, and restoring saved instance state bundles.onStart(): Called when the Activity becomes visible to the user, but not yet interactive. Useful for registering UI-observing listeners.onResume(): Called when the Activity enters the Resumed (Active/Running) state. The Activity is in the foreground, has input focus, and receives user touch/keystroke events. Camera previews and animations start here.onPause(): Called when the Activity is partially obscured (e.g., by a runtime permission dialog, semi-transparent activity, or multi-window split screen). The activity is visible but has lost input focus. Never execute slow database writes here!onStop(): Called when the Activity is no longer visible (user pressed Home or navigated to another full-screen Activity). Heavy resources, hardware sensors, and location tracking must be released here.onRestart(): Called when an activity in the Stopped state is being re-displayed before onStart().onDestroy(): Final cleanup callback before the Activity object is destroyed by calling finish() or reclaimed by the OS to free memory.| Activity State | Visibility Status | Input Focus | OS Kill Priority | Permitted Operations |
|---|---|---|---|---|
| Resumed (Running) | 100% Foreground Visible | Has Full User Focus | Lowest (Killed only in extreme panic) | Active rendering, real-time gesture input, video playback |
| Paused | Partially Visible (Obscured) | Lost Focus | Low | Pause audio, freeze animations, release camera capture |
| Stopped | Completely Hidden (0% Visible) | No Focus | Moderate (Eligible for process kill) | Persist draft data, unregister GPS/Bluetooth listeners |
| Destroyed | Terminated / Removed | None | Killed & Evicted from Memory | Cancel background coroutines, close DB connections |
When device configuration changes (screen orientation rotation, system dark mode toggle, language swap, or multi-window resizing), Android completely destroys and recreates the active Activity:
onPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume().
This ensures the app automatically loads alternative resource qualifiers (e.g., res/layout-land/ or res/values-night/).
To prevent data loss during recreation or OS process death, Android provides state persistence hooks:
onSaveInstanceState(outState: Bundle): Writes transient UI state (e.g., scroll positions, unsubmitted form inputs) before the activity is destroyed.savedInstanceState in onCreate(): Reads the persisted bundle upon recreation to restore UI state seamlessly.package com.devascent.lifecycle
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.devascent.databinding.ActivityLifecycleBinding
class LifecycleLoggingActivity : AppCompatActivity() {
private lateinit var binding: ActivityLifecycleBinding
private var timerSeconds: Int = 0
private val tag = "ActivityLifecycle"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLifecycleBinding.inflate(layoutInflater)
setContentView(binding.root)
// Restore transient state after configuration change / process kill
if (savedInstanceState != null) {
timerSeconds = savedInstanceState.getInt("KEY_TIMER", 0)
Log.d(tag, "onCreate: Restored timer state = $timerSeconds")
} else {
Log.d(tag, "onCreate: Fresh activity initialization")
}
}
override fun onStart() {
super.onStart()
Log.d(tag, "onStart: Activity becoming visible")
}
override fun onResume() {
super.onResume()
Log.d(tag, "onResume: Activity interactive and in foreground")
}
override fun onPause() {
super.onPause()
Log.d(tag, "onPause: Losing input focus")
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("KEY_TIMER", timerSeconds)
Log.d(tag, "onSaveInstanceState: Transient state saved to Bundle")
}
override fun onStop() {
super.onStop()
Log.d(tag, "onStop: Activity hidden from view")
}
override fun onDestroy() {
super.onDestroy()
Log.d(tag, "onDestroy: Activity resource teardown")
}
}
An Intent is an asynchronous messaging object used to request an action from another app component or third-party application. There are two fundamental intent types:
Intent(this, DetailActivity::class.java)). Typically used to navigate internally within your own application sandbox.Intent.ACTION_VIEW, Intent.ACTION_SEND, Intent.ACTION_DIAL) alongside a URI data payload and MIME type. The Android OS queries all installed apps via their <intent-filter> declarations and displays the system App Chooser.
Primitive data types are bundled via intent.putExtra("key", value). For custom Kotlin objects, Android requires serialization:
Serializable: Reflection-based standard Java serialization. Extremely slow on Android due to extensive runtime garbage collection and object allocation overhead.Parcelable: High-performance IPC serialization mechanism designed specifically for Android's Binder bus. Explicitly writes fields into a flattened binary memory chunk. Always use @Parcelize via the Kotlin Parcelize plugin!package com.devascent.navigation
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import kotlinx.parcelize.Parcelize
@Parcelize
data class StudentEnrollment(
val studentId: String,
val courseTitle: String,
val tuitionFee: Double
) : Parcelable
class EnrollmentHostActivity : AppCompatActivity() {
// 1. Modern Activity Result API (replaces deprecated startActivityForResult)
private val paymentLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
val transactionId = result.data?.getStringExtra("KEY_TXN_ID")
// Process confirmed enrollment
}
}
fun launchExplicitCheckout() {
val enrollment = StudentEnrollment("STU-882", "Android Mobile Track", 499.00)
// Explicit Intent: Directly targets destination class
val intent = Intent(this, PaymentCheckoutActivity::class.java).apply {
putExtra("EXTRA_ENROLLMENT_DATA", enrollment)
}
paymentLauncher.launch(intent)
}
fun launchImplicitBrowser() {
// Implicit Intent: System resolves web browser app
val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://devascentacademy.me"))
if (webIntent.resolveActivity(packageManager) != null) {
startActivity(webIntent)
}
}
}
A Service is an application component that executes background tasks without an interactive user interface:
WorkManager for reliable, deferred background tasks.bindService() and IBinder), allowing activities to interact, query, and perform IPC with the service.Enables apps to receive system or custom broadcast events:
AndroidManifest.xml via <receiver>. Heavily restricted in modern Android to prevent apps waking up simultaneously on battery/network broadcasts.registerReceiver() with an IntentFilter. Must be unregistered in onStop() or onDestroy() to prevent memory leaks.Manages shared structured data stores across application sandboxes:
content://com.devascent.provider/courses/101 (Scheme, Authority, Path, ID).contentResolver.query(), insert(), update(), and delete(), receiving standard tabular Cursor results.
Context is the universal interface to global application environment information, file storage, asset loading, system services, and theme resolution. Passing the wrong context type is the #1 leading cause of memory leaks in Android apps:
this): Tied directly to the lifecycle of a single Activity window. Contains references to the UI view hierarchy, window decorations, and current theme styling. Never store an Activity context inside a static variable or long-lived singleton! Doing so retains the entire Activity and all inflated Views in memory after onDestroy(), causing huge memory leaks.applicationContext): Tied to the lifecycle of the overall application process. Survives across Activity recreations and configuration changes. Safe for singletons, database managers, and background workers. Cannot inflate themed dialogs!| Operation / Task | Activity Context | Application Context | Critical Architectural Rule |
|---|---|---|---|
| Show AlertDialog / Toast | ✅ YES (Required for Dialogs) | ❌ NO (Throws BadTokenException) | Dialogs require a valid Window Token present only in Activity |
| Inflate XML Layout | ✅ YES (Applies custom Theme) | ⚠️ Partial (Applies default system theme) | Use Activity context to ensure correct color styling |
| Singleton / Database Manager | ❌ NEVER (Severe Memory Leak) | ✅ YES (Matches Process Lifespan) | Singletons live forever; referencing an Activity leaks it |
| Register Dynamic BroadcastReceiver | ✅ YES (Unregister in onStop) | ✅ YES (For process-wide listeners) | Always mirror register/unregister pairs precisely |
Below is a fully functional dual-activity workflow demonstrating Parcelable data bundling, explicit navigation via the modern ActivityResultContracts API, two-way result communication, and comprehensive lifecycle Logcat tracking.
package com.devascent.checkout
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.devascent.databinding.ActivityOrderBinding
import com.devascent.databinding.ActivityPaymentBinding
import kotlinx.parcelize.Parcelize
// 1. Parcelable Domain Model
@Parcelize
data class CourseOrder(
val orderId: String,
val courseTitle: String,
val amount: Double
) : Parcelable
// 2. Primary Order Activity
class OrderActivity : AppCompatActivity() {
private lateinit var binding: ActivityOrderBinding
private val logTag = "DevAscentComponent"
// Register Activity Result Contract for modern callback handling
private val checkoutLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
val paymentRef = result.data?.getStringExtra("EXTRA_PAYMENT_REF") ?: "UNKNOWN"
binding.statusLabel.text = "Enrollment Complete! Ref: $paymentRef"
Log.i(logTag, "OrderActivity: Payment acknowledged with reference: $paymentRef")
} else {
binding.statusLabel.text = "Checkout Cancelled by Student"
Log.w(logTag, "OrderActivity: Checkout aborted")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityOrderBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnProceedCheckout.setOnClickListener {
val order = CourseOrder("ORD-2026-99", "Unit 5: Android Components", 199.99)
// Build Explicit Intent with Parcelable Payload
val intent = Intent(this, PaymentActivity::class.java).apply {
putExtra("EXTRA_ORDER_PARCEL", order)
}
Log.d(logTag, "OrderActivity: Launching PaymentActivity for result")
checkoutLauncher.launch(intent)
}
}
}
// 3. Secondary Destination Activity
class PaymentActivity : AppCompatActivity() {
private lateinit var binding: ActivityPaymentBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPaymentBinding.inflate(layoutInflater)
setContentView(binding.root)
// Retrieve Parcelable object
val order: CourseOrder? = intent.getParcelableExtra("EXTRA_ORDER_PARCEL")
binding.txtCourseTitle.text = order?.courseTitle ?: "No Order Data"
binding.txtTotalDue.text = "Total Due: $${order?.amount}"
binding.btnConfirmPayment.setOnClickListener {
// Return result intent to caller
val resultIntent = Intent().apply {
putExtra("EXTRA_PAYMENT_REF", "TXN-SUCCESS-7718")
}
setResult(RESULT_OK, resultIntent)
finish() // Destroy activity and pop off backstack
}
}
}
onPause() and onStop()?onPause() indicates the Activity has lost input focus but is still partially visible to the user (e.g., when a dialog, runtime permission prompt, or multi-window split screen overlays the activity). In contrast, onStop() is invoked when the Activity is completely hidden from view (e.g., a new full-screen activity was launched or the user pressed Home). CPU-intensive teardowns and memory releases belong in onStop(), never in onPause().
onPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume(). This guarantees proper resource qualifier reloading (e.g., swapping layout-port/ for layout-land/). Transient UI state is preserved using onSaveInstanceState(outState: Bundle) or by retaining business logic in architectural Jetpack ViewModels, which survive configuration changes.
Intent(this, TargetActivity::class.java)), ensuring the intent resolves strictly to an internal application class. An Implicit Intent specifies an abstract action (e.g., ACTION_VIEW, ACTION_SEND) and a data URI/MIME type, asking the Android OS PackageManager to resolve any installed third-party app matching the criteria via declared <intent-filter> elements.
Parcelable preferred over Java Serializable for inter-process communication (IPC)?Serializable is a marker interface that relies heavily on Java Reflection to serialize object graphs, creating hundreds of short-lived temporary objects and inducing severe Garbage Collection pauses. Android Parcelable is engineered explicitly for high-speed binary memory copying across the Linux Binder IPC boundary. Parcelable generates explicit bytecode serialization, achieving 10x–20x higher performance.
startActivityForResult() deprecated, and what replaced it?startActivityForResult(intent, requestCode) was tightly coupled to a single monolithic callback method (onActivityResult), requiring giant nested switch/case blocks for request codes. Furthermore, if the launching Activity was destroyed by the OS due to memory pressure while the child activity was open, the result was frequently dropped upon recreation. It was replaced by the Activity Result API (registerForActivityResult), which decouples result handling into isolated, type-safe, lifecycle-aware contracts that automatically re-register across process death.
startForeground(id, notification)) to ensure complete transparency and user awareness that an application process is consuming device battery, CPU, or hardware sensors, and gives the process high OOM priority so the kernel does not kill it.
applicationContext be used instead?applicationContext is bound to the lifespan of the OS process itself and must be used for singletons and repositories.
content:// URIs. It allows external applications to perform CRUD operations (via ContentResolver) while allowing the host app to enforce granular read/write permissions, SQL injection filtering, and relational schema encapsulation.