A comprehensive, interview-ready guide to Android's core building blocks: the
application model and Context, the Activity and Fragment lifecycles,
Intents and broadcasting, Services and background work, and inter-process
communication. Answers merge two question banks, are written out in full with
Kotlin snippets and trade-offs, and are accurate as of 2026 (Android 15 / API 35,
targeting practices for API 34+). Where an older API has been superseded
(e.g. FragmentPagerAdapter → ViewPager2, IntentService/JobScheduler →
WorkManager), the modern alternative is explained.
- Why does an Android app lag?
- What is
Context? Application vs Activity Context - How does Zygote make Android apps start faster?
- What are the Android application components?
- What is the project structure of an Android app?
- What is
AndroidManifest.xml? - What is the
Applicationclass? - ART vs Dalvik
- What is ANR (Application Not Responding)?
- What is ADB (Android Debug Bridge)?
- Process vs Thread vs Task
- Looper, Handler, and MessageQueue
- What is an
Activityand its lifecycle? - Difference between
onCreate()andonStart() - Why call
setContentView()inonCreate()? - When is only
onDestroy()called withoutonPause()/onStop()? onSaveInstanceState()andonRestoreInstanceState()- Preventing data loss on rotation (ViewModel + saved state)
- Lifecycle sequence across multiple activities (A → B → C transparent)
- What are launch modes?
- What is a
Fragmentand its lifecycle? FragmentvsActivity— relationship and when to use each- Why use only the default constructor for a
Fragment? addvsreplaceandaddToBackStack()FragmentPagerAdaptervsFragmentStatePagerAdapter(and ViewPager2)- What is a retained
Fragment? - How to communicate between two Fragments?
- What is a
Bundle? Size limits - Transferring objects between activities — Serializable vs Parcelable
DialogvsDialogFragment
- What is an
Intent? Explicit vs Implicit - What is a
BroadcastReceiver? Types of broadcasts - How broadcasts pass messages around your app
- What is a
PendingIntent? (and Sticky Intent)
- What is a
Service? Its lifecycle - On which thread does a
Servicerun? ServicevsIntentService- What is a Foreground Service?
- What is
JobScheduler? - How does
WorkManagerguarantee task execution? - What can you use for background processing in Android?
- How can two distinct Android apps interact?
- Can an app run in multiple processes? How?
- What is AIDL? Steps to create a bound service with AIDL
- What is a
ContentProviderand when is it used?
App lag means the stutter or visual delay that occurs when the app's main thread fails to render a frame within the display's refresh rate window.
An app "lags" when it fails to render a frame within the budget the display imposes. On a 60 Hz screen you have ~16.6 ms per frame (8.3 ms at 120 Hz). If the main (UI) thread is busy past that window, the frame is janky (skipped or late), and the user perceives stutter.
Common causes:
- Heavy work on the main thread — network calls, disk/DB I/O, JSON parsing,
bitmap decoding, or large
RecyclerViewbinds running on the UI thread. - Overdraw and deep view hierarchies — the same pixel painted many times, or
nested layouts forcing repeated measure/layout passes. Flatten with
ConstraintLayout. - Garbage-collection pressure — excessive object allocation (especially in
onDraw()/onBindViewHolder()) triggers frequent GC pauses. - Inefficient lists — no view recycling, no
DiffUtil, expensive work inonBindViewHolder(). - Memory leaks — leaked activities/bitmaps shrink the available heap and increase GC.
- Layout/measure thrash, unoptimised images, and synchronous animations.
How to diagnose: Android Studio Profiler, Systrace/Perfetto, Choreographer
frame callbacks, StrictMode (to catch disk/network on the main thread), and
LeakCanary for leaks.
The fix is almost always: move work off the main thread (coroutines/Dispatchers.IO),
reduce allocations and overdraw, and keep the view hierarchy shallow.
📚 Reference: https://outcomeschool.com/blog/android-app-lag
Context means an abstract handle to the Android environment that provides access to system services, resources, and application information.
It provides access to resources (getString, getDrawable), assets, system services (getSystemService), preferences, databases, file directories, and the ability to start activities, bind services, and send broadcasts. Almost every Android API needs a Context.
Two contexts you must distinguish:
- Application Context (
applicationContext): tied to the lifecycle of the whole app process. It outlives any single activity. Use it for things that must outlive a screen — singletons, a long-lived database/Retrofit instance, or anything stored in a static/global field. Using it cannot leak an activity. - Activity Context (
thisin anActivity): tied to the activity's lifecycle and carries the activity's themed resources. Use it for UI work — inflating layouts, creating dialogs, starting another activity — where the result must respect the current theme/window.
Rule of thumb:
// Long-lived singleton -> application context (no leak)
val db = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app").build()
// UI that needs the activity theme -> activity context
AlertDialog.Builder(this).setTitle("Hi").show()Pitfall: holding an Activity context in a static field, a companion object,
or a long-lived singleton leaks the entire activity (and its view tree). Prefer
applicationContext for anything that lives beyond the screen.
📚 Reference: https://outcomeschool.com/blog/context-in-android-application
Zygote means a warm parent process that preloads core Android framework classes and resource pools at boot time to rapidly fork new app processes.
Zygote is a special process started by init at boot. It preloads and initialises the core Android framework classes and shared resources (the ART runtime, common system classes, drawables, etc.) once.
When you launch an app, Android does not start a fresh VM from scratch. Instead, Zygote forks itself. Thanks to the OS copy-on-write (COW) memory model, the new process initially shares Zygote's already-warm memory pages — the preloaded classes and resources are not re-loaded or re-initialized. Pages are only copied when the child actually writes to them.
Benefits:
- Faster startup — no re-loading/JIT-warming of framework classes per app.
- Lower memory use — shared, read-only framework pages are common across all app processes instead of duplicated.
So every app process is a child of Zygote that inherits a ready-to-run runtime, which is the core reason cold starts are not catastrophically slow.
📚 Reference: https://outcomeschool.substack.com/p/how-zygote-makes-android-apps-start
Application components means the fundamental building blocks of an Android application, consisting of Activities, Services, BroadcastReceivers, and ContentProviders.
These essential building blocks and entry points are:
- Activity — a single screen with a UI; handles user interaction.
- Service — runs long-running work in the background with no UI (e.g. music playback, syncing).
- BroadcastReceiver — responds to system-wide or app broadcast events (e.g. connectivity change, boot completed).
- ContentProvider — manages a shared set of app data and exposes it to other
apps through a
ContentResolverinterface.
Supporting pieces:
- Intent — the asynchronous message used to activate Activities, Services, and (for some) BroadcastReceivers, and to pass data between components.
Activities, Services, and (manifest-registered) Receivers, plus ContentProviders,
must be declared in AndroidManifest.xml.
📚 Reference: https://www.linkedin.com/posts/outcomeschool_outcomeschool-softwareengineer-tech-activity-7302543942028251137-S3vJ
Project structure means the layout of an Android project consisting of module configurations, source directories, and resource folders organised for the Gradle build system.
A typical project layout is as follows:
ProjectRoot/
├── app/ # an application module
│ ├── build.gradle(.kts) # module config: SDK versions, deps, build types
│ ├── proguard-rules.pro # R8/ProGuard keep rules
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java|kotlin/... # source code
│ │ └── res/ # resources
│ │ ├── drawable/ # images, vector/shape XML
│ │ ├── layout/ # XML UI layouts
│ │ ├── mipmap/ # launcher icons
│ │ ├── values/ # strings.xml, colors.xml, themes/styles
│ │ └── ...
│ ├── test/ # local JVM unit tests
│ └── androidTest/ # instrumented (on-device) tests
├── build.gradle(.kts) # top-level build config
├── settings.gradle(.kts) # included modules
├── gradle.properties # global Gradle/JVM flags
└── gradle/ # wrapper + version catalogs (libs.versions.toml)
src/mainholds production code, manifest, and resources; build-variant folders (e.g.src/debug,src/free) can override them.- Resources in
res/are referenced via the generatedRclass (R.string.app_name). - Modules keep code modular; multi-module apps add
:core,:feature_xlibrary modules.
📚 Reference: https://www.linkedin.com/posts/outcomeschool_android-app-project-structure-activity-7302902092812226560-bM8D
AndroidManifest.xml means a mandatory XML descriptor file at the root of the project that declares the app's metadata, components, permissions, and requirements to the OS.
The manifest is read by the OS and Play Store before any code runs. It declares:
- The package name / application id (identity).
- All components —
<activity>,<service>,<receiver>,<provider>— the system needs every component declared here to be able to start it. - Permissions the app requests (
<uses-permission>) and permissions it defines. - Intent filters that advertise what implicit intents a component can handle
(e.g. the launcher activity uses
MAIN+LAUNCHER). - Hardware/software requirements (
<uses-feature>), min/target SDK (usually now in Gradle), the<application>element (theme, icon, the customApplicationclass,android:exported, etc.).
<application android:name=".MyApp" android:theme="@style/AppTheme">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".SyncService" android:foregroundServiceType="dataSync" />
</application>Note: since Android 12 (API 31), any component with an intent filter must set
android:exported explicitly.
📚 Reference: https://www.linkedin.com/posts/outcomeschool_outcomeschool-softwareengineer-tech-activity-7305255303908888576-c0Nf
Application means a base class that represents the entire application process and maintains global state across all components.
A single instance is created when the process starts and lives as long as the process does — making it the natural place for process-wide initialisation and global state.
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
// Runs once, before any Activity/Service. Keep it fast (affects cold start).
Timber.plant(Timber.DebugTree())
// init DI graph, crash reporting, etc.
}
}Register it via android:name=".MyApp" in the manifest's <application> tag.
Key points:
getApplicationContext()returns this instance — a safe long-livedContext.- Useful callbacks:
onCreate(),onLowMemory(),onTrimMemory(),onConfigurationChanged(), andregisterActivityLifecycleCallbacks(). - Do not do heavy/blocking work in
onCreate()— it directly delays cold start. Prefer App Startup library or lazy init. - It is not a substitute for a DI container; avoid stuffing mutable global state here.
📚 Reference: https://www.linkedin.com/posts/outcomeschool_outcomeschool-softwareengineer-tech-activity-7304864185010528256-2GGU
ART (Android Runtime) means the modern managed runtime environment that compiles DEX bytecode using hybrid Ahead-Of-Time and Just-In-Time compilation, whereas Dalvik was the legacy runtime that compiled bytecode strictly via a Just-In-Time compiler.
Both execution environments run Android's DEX (Dalvik Executable) bytecode, but compile it differently:
- Dalvik (default up to Android 4.4): used a Just-In-Time (JIT) compiler, compiling bytecode to native code at runtime as needed. Small install footprint, faster install, but slower execution and more runtime jank.
- ART (experimental in 4.4, default since 5.0): originally used Ahead-Of-Time (AOT) compilation at install time, producing native code for faster execution and startup, at the cost of more storage and longer installs. Modern ART uses a hybrid: interpret + JIT first, with profile-guided AOT compilation done in the background (e.g. while charging) for hot code paths — combining fast installs with fast steady-state performance. ART also has improved garbage collection.
Trade-off summary: Dalvik = smaller/faster install, slower run; ART = faster run & better GC, more storage. Apps written for Dalvik generally run on ART.
ANR (Application Not Responding) means a system-triggered dialog shown to the user when the main thread of an application remains blocked for too long.
This dialog signals that the UI is frozen. Triggers:
- Input dispatch timeout — no response to an input event within ~5 seconds.
BroadcastReceiver—onReceive()not finishing within ~10s (foreground) / ~60s (background).Servicelifecycle callbacks not completing in time (~20s foreground).
Cause: long/blocking work on the main thread (network, disk, DB, heavy
computation, deadlocks). Fix: move work to background threads/coroutines, never
block the UI thread, use StrictMode to detect violations, and inspect
/data/anr/traces.txt (or Play Console ANR reports) for the stack.
ADB (Android Debug Bridge) means a versatile command-line utility that facilitates communication, debugging, and command execution between a development machine and an Android device.
It runs a client-server protocol used to install/uninstall apps, push/pull files, read logs, run a shell on the device, and debug. Common commands:
adb devices # list connected devices
adb install app.apk # install an APK
adb logcat # stream device logs
adb shell # open a shell on the device
adb shell am start -n pkg/.Activity
adb push local /sdcard/remote # copy file to deviceIt consists of a client (your machine), a daemon (adbd) running on the device, and a server process mediating between them.
Process means an isolated OS-level resource allocation boundary, thread means the smallest unit of execution within a process, and task means a collection of user-facing activities arranged in a back stack.
Details:
- Process — an OS-level execution context where each Android app normally runs in its own process with its own VM instance and isolated memory. The system can kill processes to reclaim resources. Components can be split into multiple processes via
android:processin the manifest. - Thread — the smallest unit of execution within a process. Threads in a process share memory. The main/UI thread handles UI and event dispatch; additional threads handle background work.
- Task — a user-facing concept: an ordered stack (back stack) of activities the user interacts with as they move through the app(s). A task can span multiple apps/processes and is governed by launch modes and task affinity.
In short: process = resource/isolation boundary; thread = unit of execution; task = the user's navigation journey through activities.
Looper means a helper class that keeps a thread alive to run a message loop, MessageQueue is a queue holding runnable tasks or messages, and Handler is a mechanism used to post tasks or send messages to a specific looper's queue.
These three components work together to implement Android's per-thread message loop:
- MessageQueue — a queue of
Message/Runnableitems to be processed, ordered by dispatch time. - Looper — turns a normal thread into a looping thread: it owns the
MessageQueueand continuously pulls items off it and dispatches them. The main thread has a Looper set up by the framework (Looper.getMainLooper()). - Handler — bound to a specific
Looper; you use it to postRunnables or sendMessages onto that looper's queue, optionally with a delay. This is how you schedule work to run on (and marshal results back to) a particular thread.
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post { textView.text = "Updated on UI thread" } // run on main threadTo make your own thread loop, use HandlerThread (it creates a Looper for you).
In modern code, coroutines/Dispatchers.Main largely replace manual Handler use.
Activity means a single, focused screen that presents a user interface and manages its lifecycle through a series of system-driven callbacks.
Override these callbacks to acquire or release resources at the right time.
Lifecycle callbacks (in order for a typical launch → exit):
onCreate(savedInstanceState)— created. Inflate UI (setContentView), initialize, restore state. Called once per instance.onStart()— activity becomes visible (but not yet interactive).onResume()— activity is in the foreground and interactive; at the top of the stack. (App is now in the "resumed/running" state.)onPause()— losing focus (another activity comes in front, e.g. a dialog or transparent activity). Still partially visible. Keep it quick — the next activity won't resume until this returns.onStop()— no longer visible (fully obscured or backgrounded). Release heavier resources here.onRestart()— called beforeonStart()when coming back from stopped.onDestroy()— being destroyed (user finished it, or system reclaimed it, or config change). Final cleanup.
Described as a diagram:
onCreate -> onStart -> onResume -> [RUNNING]
|
(another activity/dialog) v
onPause -> onStop -> onDestroy
| |
(back to front) onRestart -> onStart -> onResume
📚 Reference: https://developer.android.com/guide/components/activities/activity-lifecycle
onCreate() means the one-time lifecycle callback where initial setup and view inflation are performed when an activity is created, whereas onStart() is a callback invoked every time the activity becomes visible to the user.
Differences:
onCreate()is called once when the activity instance is first created. It's where one-time setup happens:setContentView(), view binding, reading thesavedInstanceStatebundle, wiring ViewModels. The activity is not yet visible.onStart()is called every time the activity becomes visible to the user — both afteronCreate()on first launch and afteronRestart()when the user returns from a stopped state. It runs more than once over the activity's life and is where you (re)start visible-only work (e.g. register a location listener you stop inonStop()).
Mnemonic: onCreate = "set up once"; onStart = "now visible, possibly again".
📚 Reference: https://developer.android.com/guide/components/activities/activity-lifecycle
setContentView() means the method that inflates the XML layout and attaches the view tree to the activity's window, which is called in onCreate() to ensure view initialisation happens only once.
It belongs in onCreate() because:
onCreate()runs once per instance — you want to build the view tree once, not on every visibility change. Putting it inonStart()/onResume()would re-inflate repeatedly, wasting work and discarding view state.- The view tree must exist before later callbacks and before the user sees the
screen;
onCreate()is the earliest, one-time point where you also have thesavedInstanceStateto restore into those views.
In Jetpack Compose, setContent { } plays the analogous role and is likewise
called in onCreate().
📚 Reference: https://www.youtube.com/watch?v=U1aHAt7XC5I
Direct destruction means destroying an activity immediately from onCreate() by calling finish(), which bypasses the visibility-related onPause() and onStop() states.
If you call finish() inside onCreate() before the activity ever becomes visible or resumed, the system goes straight from onCreate() to onDestroy().
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!userLoggedIn) {
startActivity(Intent(this, LoginActivity::class.java))
finish() // -> onDestroy() will be the next callback, no onPause/onStop
return
}
setContentView(R.layout.activity_main)
}This is common for "router"/splash activities that decide where to go and finish
immediately. (Note: onPause/onStop are skipped precisely because the activity
was never started/resumed.)
📚 Reference: https://www.youtube.com/watch?v=B2kY_ckZa-g
onSaveInstanceState() means a callback used to write transient UI state into a Bundle before destruction, whereas onRestoreInstanceState() is a callback invoked during recreation to restore the saved state.
These callbacks handle transient UI state across system-initiated destruction (such as configuration changes or process death while in the background).
onSaveInstanceState(outState: Bundle)— called before the activity may be destroyed (typically afteronStopon modern versions). Write small UI state into theBundle.onRestoreInstanceState(savedInstanceState: Bundle)— called afteronStart()when the activity is recreated, giving back the sameBundle.
The same Bundle is also passed to onCreate(savedInstanceState). Because
onCreate() runs on both fresh creation and recreation, always null-check
the bundle: null means a brand-new instance.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(KEY_QUERY, searchView.query.toString())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
savedInstanceState?.getString(KEY_QUERY)?.let { restoreQuery(it) }
}Limits & trade-offs: the bundle must stay small (it's parcelled across a
Binder; large data risks TransactionTooLargeException). It is not called on
explicit user finish (back press, finish()). For larger or longer-lived data,
use a ViewModel (survives config change but not process death) and persist
real data to disk/DB. ViewModel + SavedStateHandle covers both.
📚 Reference: https://www.linkedin.com/posts/outcomeschool_outcomeschool-softwareengineer-tech-activity-7301985158608392193-pA5M
State persistence strategy means combining ViewModel to retain rich data across configuration changes with SavedStateHandle to preserve essential transient data across process death.
On rotation (and other configuration changes) the activity is destroyed and recreated by default. Use a layered strategy:
ViewModelfor in-memory UI data. AViewModelis lifecycle-aware and is not destroyed on configuration change — the recreated activity reconnects to the same instance. Rotate three times and you have three activity instances but oneViewModel. Store fetched lists, computed state, etc. here.onSaveInstanceState()/SavedStateHandlefor small transient UI state (a search query, scroll position) that must also survive process death (which a plainViewModeldoes not).- Disk/DB (Room, DataStore) as the source of truth for anything that must truly persist.
class SearchViewModel(private val state: SavedStateHandle) : ViewModel() {
val results = MutableLiveData<List<Item>>() // survives rotation
var query: String
get() = state["query"] ?: ""
set(v) { state["query"] = v } // survives process death
}Example: keep the search query in SavedStateHandle, and the resulting list in
the ViewModel. (Alternatively, android:configChanges lets you handle a config
change yourself without recreation, but it's discouraged for orientation as it
bypasses resource re-selection.)
Lifecycle coordination means the deterministic sequence of visibility and focus transitions that occur across activities during navigation.
For example, if Activity C is transparent (not fully covering B), the sequence is:
- Go A → B (B opaque):
A.onPause→B.onCreate→B.onStart→B.onResume→A.onStop(A is fully covered, so it stops). - Go B → C (C transparent/partially visible):
B.onPause→C.onCreate→C.onStart→C.onResume. B does NOT getonStopbecause it is still partially visible behind the transparent C. - Rotate the phone (C on top): the visible/recreating activities go through
destroy→create. C:
C.onPause→C.onSaveInstanceState→C.onStop→C.onDestroy→C.onCreate→C.onStart→C.onRestoreInstanceState→C.onResume. B (visible behind, depending on OEM/version) typically also recreates similarly. (A is stopped; it recreates when next shown.) - Press back to B (finish C):
C.onPause→B.onResume→C.onStop→C.onDestroy. (B was only paused, so it just resumes — noonCreate.) - Press back to A (finish B):
B.onPause→A.onRestart→A.onStart→A.onResume→B.onStop→B.onDestroy. (A was stopped, so it restarts.)
Key insight tested here: a transparent/non-fullscreen activity pauses but does not stop the activity beneath it.
Launch modes means instructions in the manifest or intent flags that define how a new activity instance is instantiated and integrated into a task's back stack.
launchMode in the manifest and intent flags control this behaviour.
standard(default): always creates a new instance in the caller's task. Multiple instances can coexist (even of the same activity).singleTop: if an instance is already at the top of the task, the intent is delivered to it viaonNewIntent()instead of creating a new one. If it's not at the top, a new instance is created.singleTask: at most one instance exists, as the root of its task. A new intent re-uses it viaonNewIntent()and clears activities above it. Good for an app's main entry/home.singleInstance: likesingleTask, but the activity is the only member of its task — no other activities are placed in that task.
Equivalent intent flags: FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_CLEAR_TOP,
FLAG_ACTIVITY_SINGLE_TOP.
// onNewIntent fires for singleTop/singleTask re-use
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleDeepLink(intent)
}Trade-off: singleTask/singleInstance change task/back-stack behaviour and can
surprise users; reserve them for genuine single-instance screens (launchers,
auth, system-like dialogs).
📚 Reference: https://outcomeschool.com/blog/singletask-launchmode-in-android · https://youtu.be/WYkQEnm4jeI
Fragment means a modular, reusable user interface component that lives within a host activity and manages its own lifecycle distinct from the activity's instance.
It is hosted inside an activity (or another fragment). It has its own lifecycle and back stack but is always tied to its host's lifecycle. Fragments enable adaptive layouts (e.g. master-detail on tablets), view-pager pages, and navigation destinations.
A Fragment has two intertwined lifecycles: the fragment instance and its view. Key callbacks (creation order):
onAttach(context)— attached to its host.onCreate(savedInstanceState)— fragment created (no view yet); init non-view state.onCreateView(...)— inflate and return the fragment's view hierarchy.onViewCreated(view, ...)— view exists; set up views, observers.onViewStateRestored(...)— saved view state restored.onStart()→onResume()— mirror the host: visible, then interactive.
Teardown order:
onPause()→onStop().onSaveInstanceState(...).onDestroyView()— the view is destroyed (but the fragment instance may live on, e.g. on the back stack). Clear view references / binding here to avoid leaks.onDestroy()→onDetach()— fragment fully gone.
Critical nuance: the view lifecycle is shorter than the fragment lifecycle.
A fragment can be removed to the back stack — its view is destroyed
(onDestroyView) while the instance survives, then onCreateView runs again when
it returns. Always observe LiveData/flows with viewLifecycleOwner, not the
fragment, in onViewCreated.
📚 Reference: https://developer.android.com/guide/fragments/lifecycle
Activity means a top-level entry point that provides a window and a base context, whereas Fragment is a UI sub-component hosted inside an activity for flexible, modular screen designs.
Key differences:
- An Activity is a top-level, system-managed component with a window and an entry in the manifest; it's an entry point the OS can launch.
- A Fragment is a sub-component that lives inside an activity (or another fragment) and manages a piece of that activity's UI. It cannot exist on its own — it needs a host. A single activity can host many fragments and swap them.
Relationship: the activity provides the window and overall lifecycle; fragments
are modular UI chunks the activity composes and the FragmentManager controls.
When to use a Fragment rather than an Activity:
- You have UI components/flows reused across screens (one fragment, many hosts).
- You want multiple panes side-by-side (tablet master-detail,
ViewPager2tabs) within one screen. - You're using the single-activity architecture with Jetpack Navigation,
where destinations are fragments — cheaper transactions than full activities,
shared
ViewModel/scope, and simpler back-stack handling.
When an Activity is appropriate: a genuinely separate entry point, a screen launched by an external implicit intent, or a distinct task.
📚 Reference: https://stackoverflow.com/questions/10478233/why-fragments-and-when-to-use-fragments-instead-of-activities
No-argument constructor means the default constructor that the Android system uses to recreate a fragment via reflection, requiring all initial parameters to be passed via a persistent Bundle argument instead.
The framework recreates fragments using reflection by calling the no-arg constructor after a configuration change or process death. If you add a custom constructor with parameters, the system cannot call it during recreation, leading to crashes or state loss.
Correct pattern: pass arguments via a Bundle set with setArguments() (often
via a newInstance factory). The system automatically saves and restores
arguments, so the fragment is reconstructed with the same data it was created
with.
class DetailFragment : Fragment() {
companion object {
private const val ARG_ID = "id"
fun newInstance(id: Long) = DetailFragment().apply {
arguments = bundleOf(ARG_ID to id)
}
}
private val id: Long get() = requireArguments().getLong(ARG_ID)
}This guarantees the fragment is restored to the same state it was initialized with.
📚 Reference: https://www.youtube.com/watch?v=CitBt0FZFIc · https://outcomeschool.com/blog/default-constructor-to-create-a-fragment
add() means stacking a fragment on top of the container without disturbing existing fragments, replace() means removing all existing fragments from the container before adding the new one, and addToBackStack() preserves the transaction to allow back-button navigation.
Within a FragmentTransaction:
add(containerId, fragment)— adds a new fragment on top; the existing fragment(s) remain attached and their views stay. The old fragment is not destroyed and does not runonPause/onStop/onDestroyView— both can be active (and overlapping) in the same container.replace(containerId, fragment)— removes all fragments currently in the container, then adds the new one. The removed fragment runsonPause→onStop→onDestroyView(its view is gone). When you navigate back, that fragment'sonCreateViewis invoked again.
In short: with replace, lifecycle callbacks (onPause, onStop,
onCreateView, etc.) fire for the outgoing fragment; with add they don't,
because it's never removed.
addToBackStack(name): records the transaction on the back stack so pressing
Back reverses it (restoring the previous fragment state) instead of leaving
the app. Without it, a replace is irreversible by Back and the prior fragment
is lost.
supportFragmentManager.commit {
setReorderingAllowed(true)
replace(R.id.container, DetailFragment.newInstance(id))
addToBackStack("detail") // Back returns to the previous fragment
}📚 Reference: https://stackoverflow.com/questions/24466302/basic-difference-between-add-and-replace-method-of-fragment/24466345 · https://stackoverflow.com/questions/22984950/what-is-the-meaning-of-addtobackstack-with-null-parameter
ViewPager adapters means legacy classes that manage fragment pages: FragmentPagerAdapter keeps all instances in memory, FragmentStatePagerAdapter destroys instances to save memory, and ViewPager2 is their modern replacement built on RecyclerView.
These were the two adapters for the legacy ViewPager:
FragmentPagerAdapter— keeps every visited fragment instance in memory; only the view is destroyed when off-screen (it callsdetach(), notremove()). Revisiting recreates the view but reuses the fragment instance. Best for a small, fixed number of pages (e.g. a few tabs). Can use a lot of memory if there are many pages.FragmentStatePagerAdapter— destroys the fragment instance when it's off-screen, saving only itssavedInstanceState, and recreates it on revisit. Far lower memory; best for a large or unknown number of pages.
Modern alternative (2026): both are deprecated. Use ViewPager2 with a
FragmentStateAdapter (which behaves like the old FragmentStatePagerAdapter
— it saves/restores state and recreates fragments as needed). ViewPager2 is
backed by RecyclerView, supports vertical paging, RTL, and DiffUtil-style
updates.
class PagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
override fun getItemCount() = 3
override fun createFragment(position: Int): Fragment = when (position) {
0 -> HomeFragment()
1 -> SearchFragment()
else -> ProfileFragment()
}
}
viewPager2.adapter = PagerAdapter(this)Migration mapping: getCount() → getItemCount(), getItem() →
createFragment(); whether you used FragmentPagerAdapter or
FragmentStatePagerAdapter, you now use FragmentStateAdapter. Use
setOffscreenPageLimit if you need adjacent pages kept alive.
📚 Reference: https://developer.android.com/develop/ui/views/animations/vp2-migration
Retained fragment means a legacy fragment that had setRetainInstance(true) enabled so its instance survived configuration changes, which is now deprecated in favour of ViewModel.
Historically this was used to hold expensive objects (threads, bitmaps, network state) across rotation without re-fetching. Only its view goes through onDestroyView/onCreateView on rotation.
Important (2026): setRetainInstance is deprecated. The recommended
replacement is ViewModel, which is purpose-built to survive config changes,
is scoped correctly, and avoids the leak/edge-case pitfalls of retained
fragments (which couldn't be added to the back stack and complicated nested
fragments). Mention the legacy concept, but state you'd use a ViewModel today.
// Modern equivalent of "retained" data
class MyViewModel : ViewModel() { /* survives rotation, cleared on real finish */ }
private val vm: MyViewModel by viewModels()📚 Reference: https://www.linkedin.com/posts/outcomeschool_softwareengineer-androiddev-android-activity-7265620144289193984-hlpH
Fragment communication means transferring data between fragments using a shared, activity-scoped ViewModel or the dedicated Fragment Result API to maintain loose coupling.
Fragments should never talk to each other directly (it couples them and breaks reuse). Options, preferred first:
-
Shared
ViewModelscoped to the host activity — both fragments obtain the sameViewModelviaactivityViewModels()and communicate through it (LiveData/StateFlow). The cleanest, lifecycle-safe approach.// In both fragments: private val shared: SharedViewModel by activityViewModels() // Fragment A: shared.select(item) // Fragment B observes: shared.selected.observe(viewLifecycleOwner) { render(it) }
-
Fragment Result API — for one-off results between a fragment and its parent/sibling without a shared ViewModel:
// Receiver (set in onCreate/onViewCreated): parentFragmentManager.setFragmentResultListener("key", viewLifecycleOwner) { _, bundle -> val value = bundle.getString("value") } // Sender: parentFragmentManager.setFragmentResult("key", bundleOf("value" to "hi"))
-
Via the host activity through an interface (older pattern): the fragment defines a callback interface that the activity implements; the activity relays to the other fragment. Verbose and tightly coupled — prefer the above.
Avoid findFragmentById/casting to call another fragment's methods directly.
Bundle means a key-value mapping class designed to pass parcelable data across IPC boundaries, limited to a total Binder transaction buffer of approximately 1 MB.
It stores values via the Parcelable mechanism (it serialises to a Parcel), which is why it's used for Intent extras, fragment arguments, and onSaveInstanceState.
- Used in:
Intent.putExtra/extras,Fragment.setArguments,onSaveInstanceState/onRestoreInstanceState,startActivityForResultresults, etc. - Supports primitives,
String,CharSequence,Parcelable,Serializable, arrays, and nestedBundles.
Size limit / pitfall: a Bundle passed via IPC goes through a Binder
transaction whose buffer is ~1 MB total per process (practically you should
keep a single transaction well under that — a few hundred KB at most). Exceeding
it throws TransactionTooLargeException. Never put bitmaps, large lists, or
big blobs in a Bundle/Intent — pass an id/URI and load the data from a
repository/DB instead.
Parcelable means an Android-specific serialisation interface optimised for fast IPC without reflection, whereas Serializable is a standard Java interface that relies on slower reflection-based serialisation.
Put data in Intent extras / a Bundle. For custom objects you need either Serializable or Parcelable:
Serializable— a Java marker interface; serialisation is automatic but uses reflection, which is slow and creates many temporary objects (GC pressure). Easy to use, poor performance.Parcelable— an Android-specific interface optimised for IPC; you describe how to write/read fields, so no reflection → significantly faster and more memory-efficient. The recommended approach on Android.
Use the @Parcelize Kotlin plugin to generate the boilerplate:
@Parcelize
data class User(val id: Long, val name: String) : Parcelable
startActivity(Intent(this, DetailActivity::class.java).putExtra("user", user))
// Receiver:
val user = intent.getParcelableExtra("user", User::class.java) // API 33+ typed overloadTrade-off: Parcelable is faster but more verbose (mitigated by @Parcelize);
Serializable is simpler but slower. Remember the Bundle size limit — pass ids,
not large payloads. (transient can be used to exclude a field from Java
serialisation.)
DialogFragment means a fragment wrapper around a dialog that integrates with the FragmentManager to survive configuration changes, whereas Dialog is a basic floating window that lacks lifecycle awareness.
Differences:
Dialog— a basic floating window. It is not lifecycle-aware and is effectively owned by the activity directly; on a configuration change (rotation) it is dismissed/leaks because nothing re-creates it.DialogFragment— wraps a dialog inside a fragment, so it participates in the fragment lifecycle andFragmentManager. It survives configuration changes (re-created and re-shown automatically), handles its own state, and integrates with the back stack.
Recommendation: always use DialogFragment for dialogs that should persist
across rotation. Use a bare Dialog only for trivial, transient prompts.
class ConfirmDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
AlertDialog.Builder(requireContext())
.setTitle("Delete?")
.setPositiveButton("Yes") { _, _ -> /* ... */ }
.setNegativeButton("Cancel", null)
.create()
}
ConfirmDialog().show(supportFragmentManager, "confirm")📚 Reference: https://stackoverflow.com/questions/7977392/android-dialogfragment-vs-dialog
Intent means a messaging object used to request an action from another component, where explicit intent specifies the target component class and implicit intent declares a generic action to perform.
An Intent carries the action, optional data (Uri), category, type, and extras (a Bundle).
Explicit Intent — names the exact target component (by class/package). Used for navigation within your app.
val intent = Intent(this, ActivityTwo::class.java)
intent.putExtra("id", 42)
startActivity(intent)Implicit Intent — declares an action to perform without naming a
component; the system finds any app whose intent-filter can handle it (and
shows a chooser if multiple match). Used to leverage other apps (open a URL,
share, dial).
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://google.com"))
if (intent.resolveActivity(packageManager) != null) startActivity(intent)Note (Android 11+): to query/launch other apps you may need a <queries> element
in the manifest due to package-visibility restrictions.
📚 Reference: https://developer.android.com/guide/components/intents-filters
BroadcastReceiver means a component that listens for and responds to system-wide or application-specific event notifications.
It has a single entry point, onReceive(context, intent), which must finish quickly (it runs on the main thread; long work risks an ANR — offload to goAsync() or WorkManager).
Registration:
- Manifest-declared (static) — declared in
AndroidManifest.xml. Heavily restricted since Android 8 (Oreo): most implicit system broadcasts can no longer be received via the manifest (use explicit, or runtime registration, orWorkManager/JobSchedulerconstraints instead). - Context-registered (dynamic) — registered/unregistered at runtime with
registerReceiver(); only active while registered. Android 13+ requires specifyingRECEIVER_EXPORTED/RECEIVER_NOT_EXPORTED.
Types of broadcasts:
- Normal broadcasts (
sendBroadcast) — asynchronous, delivered to all matching receivers in undefined order; can't be aborted. - Ordered broadcasts (
sendOrderedBroadcast) — delivered one receiver at a time in priority order; a receiver can pass results along or abort the broadcast. - Local broadcasts — historically
LocalBroadcastManagerfor in-app-only events; now deprecated — use a sharedViewModel/Flow/LiveDataor observable pattern instead. - Sticky broadcasts —
sendStickyBroadcast, also deprecated/removed (the last value "stuck" for future receivers). - System broadcasts — sent by the OS (e.g.
BOOT_COMPLETED,CONNECTIVITY_CHANGE).
val receiver = object : BroadcastReceiver() {
override fun onReceive(c: Context, i: Intent) { /* handle */ }
}
ContextCompat.registerReceiver(
this, receiver, IntentFilter(Intent.ACTION_BATTERY_LOW),
ContextCompat.RECEIVER_NOT_EXPORTED
)📚 Reference: https://developer.android.com/guide/components/broadcasts
Broadcast messaging means a publish-subscribe communication mechanism where publishers send intents and registered receivers consume them via onReceive().
Broadcasts are built on Intents:
- A sender builds an
Intentdescribing an event (action + optional extras) and callssendBroadcast(intent)(or ordered/explicit variants). - The system matches the intent against registered receivers — via
IntentFilters (manifest or runtime) or an explicit component target. - Each matching
BroadcastReceiver.onReceive()runs with the intent, reads the extras, and reacts.
This lets a Service notify the UI, or one part of the app signal another,
without holding direct references. Classic example: a download Service
broadcasts "download complete" and an Activity's receiver updates the UI.
Modern guidance: for purely in-app messaging, prefer a shared
ViewModel exposing a StateFlow/LiveData, or an app-scoped Flow — it's
lifecycle-aware, type-safe, and avoids the overhead and Oreo+ restrictions of
broadcasts. Reserve broadcasts for genuine cross-app/system events.
// Service signals UI (explicit, in-app)
sendBroadcast(Intent("com.app.DOWNLOAD_DONE").setPackage(packageName).putExtra("id", id))PendingIntent means a token that wraps an intent and grants another application or the system permission to execute it later using the sender's identity and privileges.
It allows another app or the system to fire the wrapped intent on your behalf, even if your process is dead at that time.
Common uses: notification actions/content, alarms (AlarmManager),
App Widgets, and geofencing/location callbacks.
Factory methods: getActivity(), getService(), getBroadcast(),
getForegroundService().
val intent = Intent(this, DetailActivity::class.java).putExtra("id", 7)
val pi = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE // immutable required (API 31+)
)
notificationBuilder.setContentIntent(pi)Note: since Android 12 (API 31) you must explicitly set
FLAG_IMMUTABLE or FLAG_MUTABLE.
Sticky Intent (related concept): an old broadcast (sendStickyBroadcast)
whose last value "stuck" with the system so future registrants got the most
recent value immediately (e.g. ACTION_BATTERY_CHANGED). It is deprecated
because it had no delivery guarantees or security. Don't use it in new code.
Service means an application component that performs long-running background tasks without displaying a user interface.
It can keep running even when the user switches away (subject to background limits). It does not create its own thread — by default it runs on the main thread. A component can bind to a service to interact with it via IPC.
Two ways to run a service, with two lifecycle paths:
- Started service (
startService/ContextCompat.startForegroundService):onCreate()(once) →onStartCommand()(each start request) → runs untilstopSelf()orstopService()→onDestroy().onStartCommandreturns a restart policy:START_STICKY,START_NOT_STICKY, orSTART_REDELIVER_INTENT.
- Bound service (
bindService):onCreate()→onBind()(returns anIBinder) → clients interact → last clientunbindService()→onUnbind()→onDestroy().
A service can be both started and bound; it lives until it's neither started nor bound.
class MyService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// do work (on a background thread!), then maybe stopSelf()
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() { /* cleanup */ }
}Modern context: background started-services are heavily restricted (Android 8+);
for deferrable/guaranteed background work prefer WorkManager. Use a
foreground service (Q38) for user-visible ongoing work like playback or
navigation.
📚 Reference: https://developer.android.com/guide/components/services · https://www.linkedin.com/posts/amit-shekhar-iitbhu_softwareengineer-androiddev-android-activity-7265212180570992640-CJn_
Service thread execution means that services run on the application's main thread by default, requiring background threads or coroutines to perform blocking work.
A service is not a separate thread or process. Doing blocking work directly in onStartCommand() or onBind() will block the UI and cause an ANR.
To do real work, create your own background thread / coroutine inside the service:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
CoroutineScope(Dispatchers.IO).launch {
doWork()
stopSelf(startId)
}
return START_NOT_STICKY
}(Exception/aside: IntentService — now deprecated — created its own worker
thread automatically; see Q37.) If you set android:process in the manifest, the
service runs in a separate process, but still on that process's main thread by
default.
Service vs IntentService means the distinction between a standard Service running on the main thread, and the deprecated IntentService which ran tasks sequentially on a worker thread.
IntentService is a deprecated subclass of Service that automatically created a worker thread to process tasks sequentially and stopped itself when done, whereas a standard Service runs on the main thread and must be stopped manually.
Differences:
Service— runs on the main thread; you manage threading yourself; can be started and/or bound; handles multiple, concurrent or long-lived tasks.IntentService— created its own background worker thread, processed start intents sequentially inonHandleIntent(), and stopped itself automatically when the queue was empty.
Summary: IntentService = short async tasks on a built-in worker thread,
auto-stops; Service = long/flexible work, your own threading, manual stop.
Modern note (2026): IntentService is deprecated. Its use cases are now
served by WorkManager (deferrable/guaranteed background work) or
JobIntentService (also deprecated) / a coroutine inside a foreground
service for immediate work. Don't use IntentService in new code.
📚 Reference: https://stackoverflow.com/questions/15524280/service-vs-intentservice-in-the-android-platform
Foreground service means a high-priority service that performs work visible to the user and must display a persistent notification.
Examples include music playback, navigation, active file upload/download, and fitness tracking. Because the user sees it, the system does not subject it to the same background-execution limits.
Modern requirements (Android 9+ → 14):
- Start with
ContextCompat.startForegroundService(...)and callstartForeground(id, notification)within ~5 seconds, or the system kills the app with an exception. - Needs the
FOREGROUND_SERVICEpermission. - Android 14 (API 34): you must declare a
foregroundServiceTypeon the<service>in the manifest and hold the matching per-type permission (e.g.FOREGROUND_SERVICE_LOCATION,FOREGROUND_SERVICE_MEDIA_PLAYBACK,FOREGROUND_SERVICE_DATA_SYNC, etc.). CallingstartForegroundwith a type you didn't declare throws aSecurityException/MissingForegroundServiceTypeException. Multiple types combine with|. Some types also require runtime permission first (e.g. location). Google Play additionally requires declaring FGS usage in the Play Console.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<service android:name=".PlayerService"
android:foregroundServiceType="mediaPlayback" android:exported="false" />val notif = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_play).setContentTitle("Playing").build()
ServiceCompat.startForeground(this, 1, notif, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)Trade-off: high priority and survivability, but a mandatory visible notification
and strict policy/permission rules. For deferrable work that doesn't need to be
immediate, prefer WorkManager (which can promote itself to a foreground service
for long tasks).
📚 Reference: https://developer.android.com/develop/background-work/services/fgs/service-types · https://www.linkedin.com/posts/outcomeschool_foreground-service-in-android-activity-7303268432030879745-TFVI
JobScheduler means a system service that schedules background jobs to run under specific system constraints, batching tasks to optimise battery consumption.
The OS batches jobs across apps to save battery and intelligently picks when to run them. You define a JobService and submit a JobInfo.
val job = JobInfo.Builder(JOB_ID, ComponentName(this, MyJobService::class.java))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setRequiresCharging(true)
.build()
(getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler).schedule(job)Modern note (2026): for most apps, WorkManager is the recommended API —
it is built on top of JobScheduler (and older mechanisms) under the hood,
adds persistence/guarantees, simpler chaining, and a unified API across OS
versions. Use raw JobScheduler only for special low-level needs; otherwise
prefer WorkManager.
📚 Reference: https://developer.android.com/reference/android/app/job/JobScheduler
WorkManager means a persistent Jetpack background library that schedules deferrable, guaranteed tasks and stores their states in a local SQLite database to survive app restarts and reboots.
The work runs eventually even across app exits, device reboots, and process death, optionally under constraints.
How it guarantees execution:
- Persistence: every enqueued
WorkRequestis written to an internal Room/SQLite database. The work survives the app being killed or the device rebooting —WorkManagerreschedules persisted work after reboot (via a boot receiver). - OS scheduler delegation: under the hood it uses the best available
scheduler for the API level —
JobScheduleron API 23+ (and historicallyAlarmManager+ broadcast receivers /GcmNetworkManageron older versions) — so the OS actually runs the job at an appropriate time. - Constraints & retries: you attach
Constraints(network, charging, storage, idle). If a worker returnsResult.retry(), WorkManager re-runs it with a configurable backoff policy. If constraints aren't met, it waits. - Guaranteed-to-run, not guaranteed-immediate: the promise is the work will run eventually once constraints are satisfied — it is not for exact-time or instant execution.
val work = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED).build())
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueue(work)It also supports unique work, chaining (beginWith().then()), parallel work, and
long-running tasks via setForeground(). Use it for syncing, uploads, periodic
cleanup, etc. — not for exact alarms (use AlarmManager) or instant in-process
work (use coroutines).
Background processing means a strategy where you use Kotlin coroutines for short-lived in-process tasks, WorkManager for guaranteed deferrable tasks, and foreground services for immediate user-visible tasks.
You select the appropriate background tool based on whether the work must finish, when it should run, and whether it is visible to the user:
- Kotlin Coroutines /
Dispatchers.IO— in-process async work tied to a lifecycle/ViewModelscope. Best for work that only needs to run while the app/screen is alive (network calls, DB). Not guaranteed across process death. WorkManager— deferrable, guaranteed work that must survive app exit/reboot, optionally under constraints (sync, upload, periodic). The default recommendation for persistent background tasks.- Foreground Service — immediate, user-visible, ongoing work (playback, navigation, live location). High priority, requires a notification + FGS type.
JobScheduler— low-level constrained job scheduling (usually via WorkManager instead).AlarmManager— exact or inexact time-based triggers (setExactAndAllowWhileIdlefor alarms/reminders). Needs the exact-alarm permission on Android 12+/13+.- Deprecated/avoid:
AsyncTask,IntentService,Loader,LocalBroadcastManager.
Rule of thumb (2026): instant + only-while-alive → coroutines; must run eventually/with constraints → WorkManager; user-aware ongoing → foreground service; exact time → AlarmManager.
📚 Reference: https://developer.android.com/guide/background
App interaction means communication between separate applications using implicit intents, content providers, shared databases, or bound services over AIDL.
Several mechanisms are available depending on coupling and security:
-
Implicit Intents + intent filters — the most common, loosely coupled way. One app sends an action (
ACTION_VIEW,ACTION_SEND, custom action); another app that declared a matchingintent-filterhandles it. Great for "share to", "open with", deep links. Can pass small data via extras and get a result via the Activity Result API.val send = Intent(Intent.ACTION_SEND).apply { type = "text/plain"; putExtra(Intent.EXTRA_TEXT, "Hello") } startActivity(Intent.createChooser(send, "Share via"))
-
ContentProvider+ContentResolver— share structured data (rows, files) between apps with URI-based, permission-controlled access. -
Bound Service via AIDL / Messenger — for ongoing, programmatic IPC (method calls) across apps/processes (see Q44).
-
Broadcasts — send an explicit/permission-protected broadcast another app receives.
-
FileProvider— share files securely viacontent://URIs with temporary grants.
Android 11+ package visibility rules mean you often need a <queries> entry
to see/launch other apps. Protect cross-app surfaces with permissions and
correct android:exported settings.
📚 Reference: https://developer.android.com/training/basics/intents
Multi-process app means an application configured to run separate components in isolated process spaces using the android:process attribute in the manifest.
By default all components run in one process, but you can place a component in another process with android:process in the manifest:
<service
android:name=".SyncService"
android:process=":sync" /> <!-- private process -->
<provider
android:name=".DataProvider"
android:process="com.example.shared" /> <!-- global, can be shared with other apps -->- A name starting with
:creates a private process local to your app (e.g.:sync). - A name starting with a lowercase letter (a full name) creates a global process that other apps (with the same shared user id / signature) could share.
Why do it: isolate crash-prone or memory-heavy work (e.g. a WebView, ML, or media process) so a crash/leak doesn't take down the main process; or stay alive when the UI process is killed.
Caveats: each process gets its own VM and its own Application instance (your
Application.onCreate runs per process — guard init accordingly). Processes have
separate memory, so you can't share objects directly — you must use IPC
(AIDL/Messenger/ContentProvider/broadcasts) to communicate, which adds complexity
and overhead. Singletons and static state are not shared across processes.
📚 Reference: https://stackoverflow.com/questions/6567768/how-can-an-android-application-have-more-than-one-process
AIDL (Android Interface Definition Language) means a compiler-generated interface that allows processes to interact via Binder-based inter-process communication.
AIDL generates the marshalling (proxy/stub) code that serialises method calls and arguments across the process boundary. Use it only when you need multiple apps/processes to call your service concurrently; otherwise, Messenger is simpler.
Steps to create an AIDL-backed bound service:
-
Define the
.aidlinterface insrc/main/aidl/..., e.g.IRemoteService.aidl:package com.example; interface IRemoteService { int add(int a, int b); }
Build generates an
IRemoteServiceJava interface with an abstractStub. -
Implement the
Stubinside aServiceand return it fromonBind():class RemoteService : Service() { private val binder = object : IRemoteService.Stub() { override fun add(a: Int, b: Int): Int = a + b } override fun onBind(intent: Intent?): IBinder = binder }
-
Declare the service in the manifest (with an action /
android:exportedif other apps will bind). -
Client binds with
bindService(), and inonServiceConnectedconverts theIBinderto the interface withIRemoteService.Stub.asInterface(binder), then calls methods:private val conn = object : ServiceConnection { override fun onServiceConnected(n: ComponentName, b: IBinder) { val service = IRemoteService.Stub.asInterface(b) val sum = service.add(2, 3) } override fun onServiceDisconnected(n: ComponentName) {} } bindService(Intent(this, RemoteService::class.java), conn, BIND_AUTO_CREATE)
Notes: only AIDL-supported types and Parcelables can cross the boundary; remote
calls may run on a Binder thread pool (so the implementation must be
thread-safe); handle DeadObjectException / RemoteException.
📚 Reference: https://developer.android.com/guide/components/aidl
ContentProvider means a component that encapsulates structured data access and exposes it securely to other applications using content URIs.
It exposes data through a standard URI-addressed interface (content://authority/path) accessed via a ContentResolver, abstracting SQLite, files, or network behind basic CRUD operations.
When it's used:
- Primarily to share data with other apps in a controlled, secure way — this is its main reason to exist. (For data used only inside your own app, you generally don't need a ContentProvider; use Room/DataStore directly.)
- To integrate with framework features that consume providers: Contacts,
MediaStore, Calendar, search suggestions,
FileProviderfor sharing files, app widgets/RemoteViewsadapters, sync adapters, and the Documents UI.
It enforces access with read/write permissions and URI permission grants
(FLAG_GRANT_READ_URI_PERMISSION), so you expose exactly what you intend.
val cursor = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI, null, null, null, null
)Trade-off: powerful and secure for cross-app/structured data and a natural fit for
content URIs and observers (notifyChange + ContentObserver), but it's
boilerplate-heavy and overkill for purely internal storage.
📚 Reference: https://www.linkedin.com/posts/outcomeschool_softwareengineer-androiddev-android-activity-7268117553040764931-64fI