Free Android Framework Interview Questions & Answers
Lifecycle, process death, services, intents, and platform limits.
All 50 questions and detailed answers are free. No account or sign-in required.
What is the pairing and ordering of the Activity lifecycle callbacks, and which pair actually indicates visibility versus foreground interactivity?
The callbacks pair symmetrically: onCreate with onDestroy, onStart with onStop, and onResume with onPause. onStart/onStop bracket whether the Activity is visible on screen, while onResume/onPause bracket whether it is in the foreground and receiving input. A common trap is treating onResume as visibility: a dialog-themed activity or split-screen partner can leave your activity visible (past onStart, before onStop) yet not resumed, so work that must stop when you lose focus belongs in onPause and work tied to being seen at all belongs in onStop.
When Activity A launches Activity B, in what interleaved order do A's and B's lifecycle callbacks fire?
A's onPause runs first, then B goes through onCreate, onStart, and onResume, and only after B is resumed does A receive onStop (and later onSaveInstanceState around that point). The naive assumption that A is fully stopped before B starts is wrong, which is why you must never do heavy or long-running work in onPause. Because B's creation happens while A is merely paused, blocking in onPause directly delays the new screen from appearing.
Which lifecycle callbacks run during a device rotation by default, and how does that differ from simply pressing Home?
On a rotation the system destroys and recreates the Activity, so you get onPause, onStop, onSaveInstanceState, onDestroy, then onCreate, onStart, onRestoreInstanceState, and onResume on the new instance. Pressing Home only takes you through onPause and onStop without onDestroy, because the instance is kept. The key insight is that a configuration change is a full recreation of the object with isFinishing false, whereas Home is a stop of the same object, which is why rotation loses field state that Home does not.
What is the difference between an explicit and an implicit Intent, and when must you use each?
An explicit Intent names the exact component by class or ComponentName and is required for launching your own internal components, since implicit resolution to non-exported internals is disallowed. An implicit Intent describes an action, category, and data, letting the system find any app that declares a matching intent filter, which is how you delegate to other apps like a browser or dialer. The trap is that an implicit Intent with no resolving app throws ActivityNotFoundException, so you should guard with resolveActivity or a try/catch and be aware package visibility rules on Android 11+ can hide otherwise-matching apps.
What is the purpose of a ContentProvider, and when do you actually need one today?
A ContentProvider exposes a structured data interface behind a content URI so other apps or system components can query, insert, update, and delete through a ContentResolver, with per-URI permissions and stable identifiers. You genuinely need one when sharing data across app boundaries, feeding system features like search suggestions, sync adapters, or the sharesheet, or exposing files via FileProvider. For purely in-app storage a ContentProvider is unnecessary overhead; Room or a plain database is simpler, so building one just to read your own data is a classic over-engineering mistake.
What is the difference between a started Service and a bound Service, and can one Service be both?
A started Service is launched with startService or startForegroundService and runs until it calls stopSelf or someone calls stopService, independent of any caller. A bound Service is connected via bindService, exposes an interface through onBind, and is torn down when the last client unbinds. A single Service can be both simultaneously, in which case it lives until it is both stopped and has no bound clients; forgetting this dual condition leads to services that never die or die too early.
Why does moving work to a background thread not, by itself, solve the problem of updating the UI, and what mechanism enforces this?
Android views may only be touched from the thread that created the view hierarchy, the main thread, because the toolkit is not thread-safe and enforces this by checking the thread in ViewRootImpl, throwing CalledFromWrongThreadException otherwise. To hand a result back you post to the main thread's Looper via a Handler, runOnUiThread, a view's post, or a coroutine dispatched to Main. The subtle part is that reading a view off-thread often appears to work by luck, so code can look correct until a real race corrupts state or crashes under load.
What role does Looper and the message queue play on the main thread, and what happens if you block it?
The main thread runs a Looper that loops forever pulling Messages and Runnables off a MessageQueue and dispatching them, and every lifecycle callback, input event, and UI update is just a message on that queue. If you run a long operation inside a callback you are occupying the loop so no further messages, including redraw and input, get processed, which the system detects as an ANR. Understanding this reveals why even a moderately slow onCreate stutters everything: you are not just slowing your code, you are starving the single queue that drives the whole UI.
What are the two classic triggers for an Application Not Responding error, and what are the relevant time thresholds?
An ANR fires when the main thread fails to respond to input dispatch within about five seconds, or when a foreground BroadcastReceiver's onReceive does not return within about ten seconds (background broadcasts allow longer), and there are additional limits for Service lifecycle execution. The common misconception is that ANRs are about total CPU work; they are about the main thread being unresponsive, so a blocking network call or a synchronized lock contended on the main thread triggers one even if overall work is small. The fix is to keep all blocking work off the main thread and return quickly from receivers, scheduling real work with goAsync or WorkManager.
What survives onSaveInstanceState, and how does that differ from what a ViewModel holds across a configuration change?
onSaveInstanceState stores a small Bundle that survives both configuration change and system-initiated process death, but it is serialized into a system Binder transaction so it must stay small and hold only primitives or Parcelables representing UI state. A ViewModel, by contrast, is retained in memory across configuration changes only and can hold large or non-serializable objects, but it is gone on process death. The correct mental model is that ViewModel handles rotation cheaply while saved state handles process death, and you combine them via SavedStateHandle rather than choosing one.
What is SavedStateHandle in a ViewModel, and why is it needed if the ViewModel already survives rotation?
SavedStateHandle is a key-value map inside a ViewModel whose contents are written into the saved instance state Bundle, so unlike the rest of the ViewModel it survives system-initiated process death and restoration. It is needed precisely because a plain ViewModel is retained only across configuration changes and is recreated from scratch after the process is killed in the background and the user returns. The nuance is that whatever you put in it is subject to the same Bundle size and Parcelable constraints as onSaveInstanceState, so it is for small critical state like a selected id or query, not large data sets.
What is a notification channel, and what breaks if you target a modern API level without creating one?
Since Android 8.0 every notification must be posted to a channel that you create with a stable id, and the channel, not the individual notification, owns user-adjustable settings like importance, sound, and vibration. If you post to a channel that does not exist on Android 8.0 or higher, the notification is silently dropped and never shown. The trap that surprises developers is that importance and other channel settings are locked in at creation and cannot be raised programmatically afterward, so shipping the wrong importance means asking users to change it in settings or recreating with a new channel id.
On Android 13 and above, what is required before your app can show notifications, and what is a common gotcha with the timing of the request?
Android 13 introduced the runtime POST_NOTIFICATIONS permission, so you must declare it and request it at runtime, and without the grant your notifications are suppressed even on valid channels. The gotcha is that the system only auto-shows the permission dialog for apps that target API 33+ and have created a channel; if you request it before creating any channel or you target an older level, behavior differs and you may need to prompt manually. Best practice is to request it in context when the user reaches a feature that benefits from notifications rather than blindly at first launch, and to handle a permanent denial gracefully by routing to settings.
How do you decide between WorkManager, a foreground Service, AlarmManager, and JobScheduler for background work?
Use WorkManager for deferrable, guaranteed work that must survive process death and reboot with constraints like network or charging, since it is the recommended abstraction and picks JobScheduler under the hood. Use a foreground Service only for work the user is actively aware of that must run right now and continuously, like media playback or an ongoing navigation. Use AlarmManager only when you need execution at a precise wall-clock time such as an alarm clock, using exact-alarm APIs and permissions, and reach for JobScheduler directly rarely, since WorkManager already wraps it; the classic mistake is spinning up a Service for deferrable work that Doze will kill anyway.
What is the difference between a manifest-declared and a context-registered BroadcastReceiver, and why do manifest receivers rarely fire for implicit broadcasts now?
A manifest-declared receiver can be launched by the system even when your app is not running, while a context-registered receiver only lives as long as the Context you registered it with and only receives while registered. Since Android 8.0, most implicit broadcasts can no longer start manifest-declared receivers as a battery measure, so those receivers effectively stop working for system-wide implicit events and you must register at runtime while active or use an explicit component or an exempted broadcast. The subtle failure mode is code that worked pre-Oreo silently going dead on newer devices, which is why WorkManager or JobScheduler is preferred for reacting to conditions like connectivity.
On Android 14, what changed for registering a context-registered BroadcastReceiver, and what must you now specify?
Starting with apps targeting Android 14, any context-registered receiver that handles non-system broadcasts must explicitly declare whether it is exported by passing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED to registerReceiver, and omitting the flag throws a SecurityException at runtime. RECEIVER_NOT_EXPORTED is the safe default that prevents other apps from delivering to your receiver. The trap is that this is a runtime crash rather than a compile error, so an untested code path can ship and blow up only on a 14 device; receivers listening solely to protected system broadcasts are exempt.
Why must most PendingIntents specify a mutability flag now, and what is the difference between FLAG_IMMUTABLE and FLAG_MUTABLE?
Since apps targeting Android 12 must pass either FLAG_IMMUTABLE or FLAG_MUTABLE when creating a PendingIntent, and omitting both throws an IllegalArgumentException, because a PendingIntent hands another app a token that runs with your identity and permissions. FLAG_IMMUTABLE means the receiving app cannot fill in or alter the wrapped Intent, which is the secure default and correct for the vast majority of notifications. FLAG_MUTABLE is only appropriate in specific cases like inline reply or bubbles where the system needs to add data such as RemoteInput results; using mutable carelessly is a real privilege-escalation risk.
What does launchMode singleTop do, and how does it interact with onNewIntent?
With singleTop, if an instance of the activity is already at the top of the target task, a new launch does not create a second instance; instead the existing instance receives the new Intent through onNewIntent while keeping its state. If the activity is not at the top, a new instance is created normally. The classic bug is reading the Intent only in onCreate and assuming onNewIntent is optional: with singleTop the reused instance never re-runs onCreate, so notification taps or search re-launches deliver stale data unless you also handle onNewIntent and update the stored Intent with setIntent.
How do singleTask and singleInstance differ in their effect on tasks and the back stack?
singleTask keeps at most one instance of the activity, rooted in a task, and a re-launch brings that task forward and delivers the Intent via onNewIntent, clearing activities above it in that task; other activities can still coexist in the same task above or below per normal rules. singleInstance is stricter: the activity is the sole member of its own task and no other activities are ever placed into that task, so anything it launches goes into a different task. The common confusion is expecting singleInstance to behave like singleTask; the give-away difference is whether other activities may share the task, which affects back navigation and how results and task switching behave.
What does FLAG_ACTIVITY_CLEAR_TOP do, and how does its behavior depend on the target activity's launch mode?
FLAG_ACTIVITY_CLEAR_TOP means if an instance of the target activity already exists in the task, all activities above it are removed and it is brought to the front. The subtlety is what happens to the target itself: with the default standard launch mode it is destroyed and recreated by default unless you also pass FLAG_ACTIVITY_SINGLE_TOP, in which case the existing instance is reused and gets the Intent via onNewIntent. This is why the idiom for returning to a home screen is CLEAR_TOP combined with SINGLE_TOP or NEW_TASK, and forgetting SINGLE_TOP causes an unexpected recreation and loss of the target's state.
What is the difference between Parcelable and Serializable on Android, and when is Serializable's cost actually acceptable?
Parcelable is Android's IPC-oriented serialization designed to marshal objects into a Parcel efficiently without heavy reflection, which is why it is preferred for passing data in Intents and Bundles. Serializable is the Java mechanism that uses reflection and generates more garbage, making it slower, though the gap matters most on hot paths and large objects. It is fine to use Serializable for small, rarely-passed objects or when a class comes from a library you cannot change, but you should never assume the marshaled size is free: both go through the Binder and both contribute to the transaction size limits.
What is TransactionTooLargeException, and why can it appear seemingly at random around onSaveInstanceState or startActivity?
All Binder transactions share a per-process buffer of roughly one megabyte, and passing too much data through an Intent extra, a saved-state Bundle, or an IPC call at once overflows it and throws TransactionTooLargeException. It feels random because the limit is the sum of concurrent transactions in the buffer, so a payload that usually fits fails when combined with other in-flight IPC, and saved-state crashes often surface only when backgrounding under memory pressure. The fix is to stop shoving large data like bitmaps or big lists through Bundles and Intents and instead pass identifiers, using a repository, ViewModel, or persisted storage to hold the actual data.
How does state survival differ across a configuration change, a system-initiated process death, and the user pressing back to finish the activity?
On a configuration change the ViewModel survives in memory and saved-instance-state is also restored, so both mechanisms carry state. On system-initiated process death the ViewModel is gone but the saved-instance-state Bundle and SavedStateHandle are restored when the user returns, which is why critical UI state must live there. When the user presses back and the activity finishes, the destruction is intentional, isFinishing is true, onSaveInstanceState is not called, and no state is expected to survive; conflating this final finish with a recreation is a frequent source of over-restoring or leaking state.
What was onRetainNonConfigurationInstance, how does it relate to ViewModel, and why avoid using it directly?
onRetainNonConfigurationInstance is the legacy hook that lets an Activity hand an arbitrary object to its next instance across a configuration change, and it is in fact the low-level mechanism the modern ViewModel machinery is built on top of. You should not call it directly today because it is a single object slot with no lifecycle scoping and it is easy to leak the old Activity or its Context through it. ViewModel with its ViewModelStore is the supported abstraction that solves the same retention problem while giving you a proper onCleared callback and preventing Context leaks.
What is the Application class lifecycle, and what is a subtle mistake developers make about how long it lives and how many exist?
The Application object is created once when the process starts, before any Activity, Service, or Receiver, and it lives for the entire lifetime of the process, so onCreate there runs once per process launch. A subtle error is assuming Application onCreate runs only when the user opens the app: the system can start your process to deliver a broadcast or run a job, invoking Application onCreate without any UI, so heavy initialization there slows every cold start including headless ones. Another trap is treating Application as a place to cache state permanently, since the process and thus the object can be killed and recreated at any time, resetting fields.
What are foreground service types, and what is the five-second startForeground rule?
When you start a service with startForegroundService, you have roughly five seconds to call startForeground and show an ongoing notification, or the system throws and may crash your app with a not-responding error for the service. Modern Android additionally requires you to declare a specific foreground service type such as location, camera, mediaPlayback, or dataSync in the manifest and pass it, and on Android 14+ the platform enforces that the type matches an allowed use case and that you hold the prerequisite permissions. The trap is starting the service but doing async setup before calling startForeground, blowing the five-second window, especially when the app is in the background where starting foreground services is itself restricted.
What is Doze mode, how does App Standby differ from it, and what happens to your background work under each?
Doze mode triggers when the whole device is stationary, unplugged, and screen-off for a while, batching wakelocks, network access, jobs, and alarms into periodic maintenance windows for the entire device. App Standby is per-app, placing individual rarely-used apps into buckets that throttle their jobs and alarms regardless of overall device state. The practical consequence is that exact timing and immediate network are unreliable in the background, so relying on a raw AlarmManager alarm or a Handler.postDelayed to fire on time in the background is a mistake; you use WorkManager, high-priority FCM, or, only when truly justified, exact-alarm permissions.
What is the OnBackPressedDispatcher, and why is overriding onBackPressed now discouraged?
OnBackPressedDispatcher lets components register OnBackPressedCallback instances in a lifecycle-aware, LIFO order so fragments, dialogs, and composables can intercept back independently and enable or disable their handling dynamically. Overriding the Activity's onBackPressed is discouraged because it centralizes logic that really belongs to individual components and because it does not participate cleanly in the predictive back system. The correct pattern is to add callbacks that are enabled only when they should consume back, letting the dispatcher fall through to the next handler otherwise, which composes far better than a single monolithic override.
What is predictive back, and what must an app do to opt in and behave correctly during the back gesture?
Predictive back is the platform feature that shows an animated preview of where a back gesture will take the user, such as the previous app or a cross-activity transition, as they swipe before committing. To participate, an app opts in with the android:enableOnBackInvokedCallback manifest flag and must migrate off the deprecated key-event style handling to the OnBackInvokedCallback and OnBackPressedDispatcher APIs, handling the progress, commit, and cancel phases. The gotcha is that legacy onBackPressed overrides and KEYCODE_BACK handling are incompatible with the predictive animation, so half-migrated apps either do not show the preview or behave inconsistently when the user cancels the gesture.
When should you use the Fragment Result API versus a shared ViewModel to pass data between fragments?
The Fragment Result API, using setFragmentResult and setFragmentResultListener on the FragmentManager, is for a one-shot handoff of a small Bundle between two fragments that do not otherwise know about each other, and it correctly buffers the result until the receiver is at least STARTED. A shared ViewModel scoped to the activity or navigation graph is better for ongoing shared state that multiple fragments observe continuously. The trap is picking the wrong tool: the Result API forces you back into Bundle size and Parcelable limits and is awkward for streaming updates, while a shared ViewModel for a single fire-and-forget result over-couples the fragments through shared mutable state.
Why is a Fragment's view lifecycle distinct from the Fragment's own lifecycle, and what bug does ignoring this cause?
A Fragment instance can outlive its view: when a fragment goes onto the back stack, onDestroyView runs and the view hierarchy is torn down while the Fragment object itself survives and can later create a fresh view in onCreateView. If you observe LiveData or collect a flow using the fragment itself as the LifecycleOwner instead of viewLifecycleOwner, you can end up with multiple observers or references to a destroyed view, causing leaks and null-view crashes. The correct practice is to use viewLifecycleOwner for anything that touches views and to null out view bindings in onDestroyView.
What are runtime permissions, and why should you not rely on a previous grant persisting or on requesting at launch?
Dangerous permissions must be requested at runtime and the user can grant or deny per app, and on modern Android a permission can be revoked by the user, reset automatically when an app is unused for a while, or granted only for the current session in the case of one-time location grants. Therefore you must check the permission with checkSelfPermission every time immediately before the sensitive operation rather than assuming a prior grant holds. Requesting everything up front at launch is both poor UX and increasingly penalized, so you request in context and handle the shouldShowRequestPermissionRationale and permanent-denial cases explicitly.
What is scoped storage, and what is the practical difference between accessing your own media, other apps' media, and arbitrary files?
Scoped storage restricts an app's direct filesystem access to its own app-specific directories plus its own contributions to shared media collections, replacing broad external-storage access. To read media created by other apps you go through MediaStore with the appropriate READ_MEDIA_IMAGES/VIDEO/AUDIO permissions on Android 13+, and to let the user pick arbitrary files you use the Storage Access Framework or the photo picker, which grant access without any broad permission. The trap is code that still constructs raw File paths into shared storage or requests the legacy WRITE_EXTERNAL_STORAGE, which is effectively ignored or unavailable on current versions, so file operations silently fail.
What are window insets, and why does hardcoding padding for the status bar or navigation bar break on modern devices?
Window insets describe the regions of the window occupied by system UI and cutouts, such as the status bar, navigation bar, display cutout, and the IME, and their sizes vary by device, orientation, gesture-versus-button navigation, and foldable state. Hardcoding a fixed status-bar height breaks because that value is not constant across devices and system UI modes, and with edge-to-edge enforced by default on newer target levels your content will draw under the bars unless you consume insets. The correct approach is to react to insets via WindowInsetsCompat and setOnApplyWindowInsetsListener, or the Compose inset APIs, applying the actual reported values rather than magic numbers.
How can returning quickly from a BroadcastReceiver's onReceive still be correct while doing more work, and what is the pitfall of spawning a thread there?
onReceive runs on the main thread and must return within its time budget, so for a manifest receiver you call goAsync to obtain a PendingResult that keeps the receiver process alive briefly while you finish on a background thread, then call finish. The pitfall is starting a raw thread or coroutine and returning: once onReceive returns and no component is active, the process becomes a candidate for immediate termination, so your background work may be killed mid-flight. For anything beyond a few seconds you should enqueue WorkManager work instead of trying to stretch the receiver's lifetime.
What determines whether the system calls onSaveInstanceState, and why can relying on it for persistence be wrong?
onSaveInstanceState is called only when the system might destroy the activity involuntarily, such as configuration changes or reclaiming memory in the background, and it is deliberately not called when the user finishes the activity by pressing back or when it is explicitly finished, because that destruction is considered intentional. It is therefore transient UI-state restoration, not durable persistence, and its data can be lost if the user swipes the app away from recents in some scenarios. The mistake is using it as a substitute for saving important user data, which belongs in a database, DataStore, or the server, with saved-instance-state reserved for ephemeral things like scroll position and input focus.
Why can two consecutive PendingIntent.getActivity calls return the same object even when their wrapped Intents differ, and how do you control this?
The system caches PendingIntents by an equality that considers the requestCode, the target, the action, data, categories, and type, but not the extras, so building a second PendingIntent that differs only in extras returns the existing one unchanged, which is why notifications sometimes carry stale data. You control this by using distinct requestCodes for distinct logical intents, or by passing FLAG_UPDATE_CURRENT to overwrite the cached extras, or FLAG_CANCEL_CURRENT to invalidate it. Combined with the mandatory immutability flag, the correct call for a fresh payload is typically FLAG_UPDATE_CURRENT together with FLAG_IMMUTABLE.
What are the background location restrictions, and why might a location request that works in the foreground return nothing in the background?
Since Android 10 background location requires the separate ACCESS_BACKGROUND_LOCATION permission granted through a distinct, more restrictive flow, and Android 11 forces that grant to happen in system settings rather than a simple dialog. Even with foreground fine location working, without the background grant your app receives location only while it has a visible activity or a location-typed foreground service. Additionally, background apps are throttled to only a few location updates per hour under batching, so a background request can appear broken when it is simply rate-limited or lacking the background permission tier.
What actually happens to the back stack and tasks when you launch an activity with FLAG_ACTIVITY_NEW_TASK, and when is it required versus harmful?
FLAG_ACTIVITY_NEW_TASK asks to place the activity into a task rather than the caller's, and it is required when launching an activity from a non-activity Context like an Application or Service that has no task of its own. However, for an activity that has no special taskAffinity, NEW_TASK does not automatically create a brand-new separate task; it may reuse an existing task with a matching affinity, bringing that whole task forward, which can surprise you by resurfacing old screens. Adding it reflexively to activity-to-activity launches is a common bug because it changes back-stack grouping and can produce confusing back navigation, so it should be used deliberately with an understanding of taskAffinity.
Why is starting a foreground service from the background restricted on recent Android versions, and what is the recommended alternative?
Starting with Android 12, apps generally cannot start a foreground service while the app is in the background, and attempts throw ForegroundServiceStartNotAllowedException except within a narrow set of allowed exemptions like responding to a high-priority FCM message, an exact alarm, or certain user-initiated cases. This closes a loophole where apps used background-started foreground services to run indefinitely. The recommended path is to schedule the work with WorkManager, which now supports long-running and expedited work, or to trigger a genuinely user-visible action, rather than assuming you can spin up a foreground service on demand from a background trigger.
How does onNewIntent interact with the activity's stored Intent, and what stale-data bug appears with deep links or notification taps?
When a singleTop or singleTask activity is re-delivered an Intent, onNewIntent fires but the activity's getIntent still returns the original Intent from creation unless you call setIntent with the new one. The bug is code that reads getIntent in onResume or elsewhere to route deep links: after re-delivery it keeps acting on the first Intent's data, so a second notification with a different id opens the wrong content. The correct pattern is to update the stored Intent by calling setIntent inside onNewIntent and to parse the incoming Intent there, ensuring subsequent reads reflect the latest launch.
Why is posting delayed work with Handler.postDelayed on the main thread unreliable for anything beyond short UI timing, and what is the deeper reason?
postDelayed only schedules a message on the main Looper's queue, so it fires only while the process is alive and the main thread is looping; if the app is backgrounded and the process is killed, or the device enters Doze, the message is simply gone and never runs. It also holds no wakelock and gives no guarantee of exact timing under queue contention. The deeper point is that postDelayed is an in-process, best-effort UI convenience, not a scheduling primitive, so durable or precise timing needs WorkManager for deferrable work or AlarmManager exact alarms for wall-clock precision, both of which persist beyond the process.
What is the correct way to observe a flow or LiveData in a Fragment so it neither leaks nor misses updates across the view lifecycle?
For LiveData you observe with viewLifecycleOwner so the observer is automatically removed at onDestroyView, avoiding duplicate observers when the view is recreated from the back stack. For a cold Flow you collect inside viewLifecycleOwner.lifecycleScope using repeatOnLifecycle with STARTED, which starts collection when the view is visible and cancels it when it stops, then restarts on return. The subtle bug is using lifecycleScope.launch with a plain collect, which keeps collecting while the fragment is in the background, wasting work and potentially touching a destroyed view, or using the fragment instead of its view lifecycle owner and accumulating stale observers.
During a configuration change, in what state is a retained Fragment or ViewModel, and why can holding a Context or View reference there be catastrophic?
Across a configuration change the ViewModel and any retained instance survive while the Activity, its views, and the resources tied to that configuration are destroyed and recreated. If the ViewModel holds a reference to the old Activity, a view, or a non-application Context, it now pins the destroyed Activity in memory, leaking it and everything it references, and any use of that Context reflects the stale configuration. The rule is that a ViewModel may only hold the application Context if any, and it must expose data for the UI to render rather than reaching back into UI objects, which is exactly why AndroidViewModel provides the application, not the activity.
Why does an Intent extra sometimes arrive null or wrong in the receiving component even though you clearly put it in, considering process boundaries and mutability?
Extras are marshaled through a Parcel across the Binder, so the receiver gets a deserialized copy, not your object, meaning any non-Parcelable field, transient state, or object identity is lost, and a custom Parcelable with a mismatched write and read order silently produces corrupted or null fields. If total extras exceed the Binder buffer you hit TransactionTooLargeException instead. Additionally, reading extras with the wrong key or a type that does not match the stored value returns null or a default with no error, so the trap is assuming pass-by-reference semantics and skipping careful Parcelable implementation and size discipline.
What determines whether a background broadcast versus a foreground broadcast, and an ordered versus a normal broadcast, affects timeliness and ANR risk?
The system dispatches broadcasts in a foreground queue with a shorter timeout of about ten seconds and a background queue with a much longer timeout, and a sender can request foreground delivery with FLAG_RECEIVER_FOREGROUND for lower latency at the cost of tighter timing. Ordered broadcasts deliver to receivers one at a time by priority, so a slow high-priority receiver delays everyone behind it and can abort the broadcast, whereas normal broadcasts fan out without ordering guarantees. The practical implication is that heavy work in any receiver risks an ANR and, for ordered broadcasts, starves later receivers, which is why receivers must be fast and offload real work.
Given both subscription_status and an access-until timestamp analog in Android's own permission model, how do one-time permission grants change the assumption that a granted permission stays granted for the session?
For location, camera, and microphone the user can choose Only this time, which grants the permission for the current use but revokes it once the app leaves the foreground for a while or its relevant components are gone, so the grant is explicitly ephemeral. This breaks the common assumption that once checkSelfPermission returns granted you can cache that result for the app session, because a later check after backgrounding can return denied without any user action in your UI. The correct discipline is to re-check immediately before each sensitive access and to be prepared to re-request, treating a prior grant as advisory rather than durable.
Why can a ViewModel scoped to a NavBackStackEntry behave differently from one scoped to the Activity, and what subtle lifecycle trap does the navigation-graph scope introduce?
A ViewModel scoped to the Activity survives as long as the Activity, so all destinations share one instance, whereas one scoped to a NavBackStackEntry is tied to that entry and is cleared when the entry is popped off the navigation back stack, giving per-destination or per-graph state. The trap is that popping and re-navigating to a destination creates a fresh entry and thus a fresh ViewModel, so state you expected to persist across navigation is reset, and conversely a nested-graph-scoped ViewModel unexpectedly outlives an individual screen. Choosing the wrong scope produces bugs where shared state either resets prematurely or lingers longer than intended, so the scope must match the intended data lifetime.
How does system-initiated process death differ from a low-memory kill and from the user swiping the app away in recents, in terms of what state restoration you should expect?
System-initiated process death, typically to reclaim memory while your app is backgrounded, preserves the saved-instance-state and SavedStateHandle so that when the user returns the framework recreates the task and restores that state, and this is the case you must design for. When the user explicitly swipes the app away from recents, the system generally clears the saved state and starts fresh next time, treating it like an intentional finish. A low-memory kill of the whole process is functionally like system-initiated death for restoration purposes, but the give-away is that developers who only test rotation never exercise the save-and-restore path that these background kills require, so real process-death bugs ship unnoticed.
Considering task affinity, allowTaskReparenting, and launch modes together, why can a single activity end up in an unexpected task and how do you reason about it deterministically?
An activity's target task is decided by a combination of the launch flags on the Intent, the activity's launchMode, its taskAffinity, and whether allowTaskReparenting lets it migrate to a task with a matching affinity when that task comes to the foreground, so no single attribute tells the whole story. For example, NEW_TASK with a matching taskAffinity can drop the activity into an existing task rather than a new one, and singleTask uses affinity to locate or create its home task, which is why an activity you expected in the current task appears grouped elsewhere in recents. Reasoning deterministically means evaluating, in order, the launchMode, then the Intent flags, then affinity and reparenting, ideally verified with the dumpsys activity output rather than assumptions, because the interactions are order-dependent and easy to get subtly wrong.
Practice all Android Framework questions interactively
Search, filter, and mark questions complete in the free Preparation Path. You can start immediately without an account.
More Android interview topics
- Kotlin interview questions
- Coroutines & Flow interview questions
- Jetpack Compose interview questions
- Android Architecture interview questions
- Testing interview questions
- Dependency Injection interview questions
- Networking interview questions
- Room & Persistence interview questions
- Android System Design interview questions
- Android Tools interview questions