Fundamentals¶
What is the Activity Lifecycle?¶
View Answer
The Activity Lifecycle is the sequence of states an Android Activity transitions through from creation to destruction.
Android manages lifecycle callbacks automatically to:
-
optimize memory usage
-
restore UI state
-
handle multitasking
-
manage interruptions
Core lifecycle methods:
-
onCreate()
-
onStart()
-
onResume()
-
onPause()
-
onStop()
-
onDestroy()
What's the difference between onStart() and onResume()?¶
View Answer
onStart(): Called when Activity becomes visible to user.
-
Window not yet in focus
-
Activity cannot receive user input
-
Use for resource allocation needed for UI visibility onResume(): Called when Activity gains focus and is ready for interaction.
-
Window is in focus
-
Can receive user input
-
Use for animations, camera, location updates Simple rule: onStart = visible, onResume = interactive.
What is savedInstanceState and when is it called?¶
View Answer
savedInstanceState is a Bundle used to preserve Activity state during predictable destruction (rotation, low memory).
Called in order:
-
onPause()
-
onSaveInstanceState()
-
onStop()
Common use cases:
-
Save fragment state
-
Save scroll position
-
Save form data
-
Save UI state Note: NOT called during activity finish() or user back press.
What happens during configuration changes (rotation)?¶
View Answer
When device is rotated or configuration changes:
-
onPause() โ onSaveInstanceState() โ onStop() โ onDestroy()
-
onCreate() โ onStart() โ onResume() The Activity is DESTROYED and RECREATED.
To preserve data:
-
Store in savedInstanceState Bundle
-
Use ViewModel (survives config changes)
-
Use retained fragments
-
Add android:configChanges to manifest Best practice: Use ViewModel for non-UI state.
How does Android handle process death?¶
View Answer
Process death occurs when Android kills app to free memory (no warning, no lifecycle callbacks).
Recovery steps when user returns:
-
OS restarts app process
-
Activity onCreate() called with savedInstanceState
-
App can restore state from Bundle Without state saving: Data loss, UI reset.
Solutions:
-
Implement onSaveInstanceState()
-
Use ViewModel + Room
-
Persist critical data to disk/database
-
Use Hilt for dependency injection
What is the exact order of lifecycle callbacks?¶
View Answer
NORMAL SEQUENCE: onCreate() โ onStart() โ onResume() โ (user interaction) โ onPause() โ onStop() โ onDestroy()
VISIBLE BUT NOT INTERACTIVE: onStart() (no onResume yet - dialog shown)
ANOTHER APP IN FOREGROUND: onPause() โ onStop() (but not onDestroy)
BACK TO APP: onStart() โ onResume()
DEVICE ROTATION: onPause() โ onSaveInstanceState() โ onStop() โ onDestroy() โ onCreate() โ onStart() โ onResume()
What's the difference between explicit and implicit intents?¶
View Answer
EXPLICIT INTENT:
-
Specifies exact component (Activity/Service)
-
Requires package name and class name
-
Use for internal app communication
-
Guaranteed to reach target
IMPLICIT INTENT:
-
No target component specified
-
System matches intent to components via intent filters
-
Used for cross-app communication
-
May show chooser if multiple matches
Example explicit: Intent(this, MainActivity::class.java)
Example implicit: Intent(Intent.ACTION_VIEW, Uri.parse("https://..."))
How do intent filters work?¶
View Answer
Intent filters declare which implicit intents a component can handle. Defined in AndroidManifest.xml inside activity/service/broadcast receiver.
Components:
-
action: What operation the component can perform
-
category: Additional flags about component
-
data: URI patterns, MIME types component accepts
Matching process:
-
System receives implicit intent
-
Matches against all declared intent filters
-
Returns list of matching components
-
Shows chooser if multiple matches
Example:
-
ACTION_VIEW + http:// MIME โ Browser
-
ACTION_VIEW + image/* MIME โ Gallery
How does intent resolution work?¶
View Answer
Intent resolution is Android's process of finding the target component for an implicit intent.
Steps:
-
Find all installed apps with matching intent filters
-
Filter by action (must match exactly)
-
Filter by category (must have all requested)
-
Filter by data (URI, MIME type must match)
If multiple matches:
-
Shows chooser dialog
-
User selects preferred app
-
System can remember preference
If no matches:
- ActivityNotFoundException thrown Optimization: Use explicit intents for internal communication.
What are common intent flags and their purposes?¶
View Answer
FLAG_ACTIVITY_NEW_TASK:
-
Starts activity in new task
-
Used with contexts without activity (Services)
FLAG_ACTIVITY_SINGLE_TOP:
-
If activity at top of stack, calls onNewIntent() instead of onCreate()
-
Prevents duplicate stack entries
FLAG_ACTIVITY_CLEAR_TOP:
-
Clears all activities above target in stack
-
Target becomes top (or recreated if not running)
FLAG_ACTIVITY_NO_HISTORY:
-
Activity won't be kept in history
-
Never appears in back button
FLAG_ACTIVITY_CLEAR_TASK:
- Clears entire task when new activity starts
What is a PendingIntent and when should you use it?¶
View Answer
PendingIntent wraps an intent to be executed later by another app. Key property: Grants permission to foreign app to execute intent with YOUR app's identity and permissions.
Common uses:
-
Notification tap actions
-
Alarm Manager
-
Widget buttons
-
Broadcast receivers
Types:
-
PendingIntent.getActivity()
-
PendingIntent.getService()
-
PendingIntent.getBroadcast() Important: Use FLAG_IMMUTABLE (Android 12+) for security. Always provide proper flags when creating.
What is the Fragment lifecycle?¶
View Answer
Fragment lifecycle is similar to Activity but with additional callbacks.
Key lifecycle methods:
-
onCreate(): Fragment created
-
onCreateView(): Create fragment UI (return layout)
-
onViewCreated(): UI created, set up views
-
onStart(): Fragment visible
-
onResume(): Fragment interactive
-
onPause(): Fragment loses focus
-
onStop(): Fragment not visible
-
onDestroyView(): UI destroyed
-
onDestroy(): Fragment destroyed
Key difference from Activity:
-
Fragments can be added/removed without destroying
-
UI lifecycle separate (onCreateView, onDestroyView)
-
Dependent on host Activity
What are the differences between Fragments and Activities?¶
View Answer
ACTIVITY:
-
Screen-level UI component
-
Entry point in manifest
-
Can exist standalone
-
Own window/UI context
-
Heavy component
FRAGMENT:
-
Reusable UI component
-
Must be hosted in Activity
-
Can be easily replaced/swapped
-
Shares Activity's window
-
Lightweight, composable
Use fragments for:
-
Modular UI components
-
Tab navigation
-
Master-detail layouts
-
Reusable screens
Use activities for:
-
Screen entry points
-
Standalone screens
-
Navigation between major app sections
How do fragments communicate with each other?¶
View Answer
APPROACH 1: Shared ViewModel (BEST PRACTICE)
-
Both fragments access same ViewModel
-
ViewModel lives in Activity scope
-
Use LiveData for reactive updates APPROACH 2: Interface Callback
-
Fragment implements listener interface
-
Activity receives callback
-
Activity communicates with other fragment
-
More verbose, legacy approach APPROACH 3: Shared Preferences / Database
-
Persist data to storage
-
Both fragments read/observe
-
Good for persistent data APPROACH 4: Bundle Arguments
-
Pass data between fragments
-
Only during fragment creation
-
Not for ongoing communication Modern approach: Use ViewModel + LiveData.
How does fragment back stack work?¶
View Answer
Fragment back stack is managed by FragmentManager.
When you call: fragmentManager.beginTransaction() .replace(R.id.container, newFragment) .addToBackStack(null) .commit()
Steps:
-
Current fragment state saved
-
New fragment created and added
-
Transaction added to back stack
-
User presses back: transaction reversed
Key methods:
-
addToBackStack(): Add to back stack
-
popBackStack(): Remove from back stack
-
addToBackStack(tag): Named back stack entry
Back navigation:
-
Pops last transaction
-
Replaces new fragment with old one
-
Restores previous fragment state Each Activity has own FragmentManager back stack.
What's the best way to pass data to a Fragment?¶
View Answer
BEST PRACTICE: Use Bundle with constants
Recommended pattern: companion object { private const val ARG_USER_ID = "user_id" fun newInstance(userId: Int) = MyFragment().apply { arguments = Bundle().apply { putInt(ARG_USER_ID, userId) } } }
In onCreate(): val userId = arguments?.getInt(ARG_USER_ID) ?: 0
Why this approach:
-
Survives configuration changes
-
Works across process death
-
Type-safe with constants
-
Replicates Android best practices
DON'T:
-
Pass large or non-Parcelable objects as arguments
-
Use constructors with parameters
-
Access fragments without factory method
What is Context and what are its types?¶
View Answer
Context is an abstract class that provides access to app resources and system services. Think of it as: "Reference to the current application state."
Primary types:
APPLICATION CONTEXT:
-
Lives entire app lifetime
-
Accessible via getApplicationContext()
-
Use for singletons, global listeners
ACTIVITY CONTEXT:
-
Tied to Activity lifecycle
-
Available via 'this' or getContext()
-
Use for UI operations, dialogs
-
Destroyed when Activity destroyed
Common uses:
-
Start activities/services
-
Get system services (LocationManager, etc)
-
Access resources (strings, drawables)
-
Create databases, SharedPreferences
-
Show toasts, dialogs
How can Context cause memory leaks?¶
View Answer
Context memory leaks occur when Activity Context is referenced by long-lived objects.
PROBLEMATIC PATTERNS:
Activity context in singletons¶
object MySingleton { var context: Context? = null // WRONG! }
Inner class references Activity¶
class MyInnerClass { // Holds Activity ref fun doWork() {} }
Static references to Activity¶
companion object { var activity: Activity? = null // WRONG! }
SOLUTIONS:
-
Use application context for singletons
-
Use static inner class + WeakReference
-
Use dependency injection
-
Never store Activity in long-lived objects Memory leak chain: Activity โ thread โ singleton โ leak
When should you use Application Context vs Activity Context?¶
View Answer
USE APPLICATION CONTEXT:
-
Get SharedPreferences
-
Create global singletons
-
Get system services used globally
-
Database creation
-
Any operation not tied to UI
DON'T use for:
-
UI operations (dialogs crash if Activity destroyed)
-
Showing toasts in some cases
-
Creating LayoutInflater
USE ACTIVITY CONTEXT:
-
Show dialogs
-
Create LayoutInflater
-
Start activities/services
-
UI-dependent operations
-
Access Activity-specific services
DON'T use for:
-
Long-lived objects (memory leak risk)
-
Singletons
-
Static references Simple rule: Use app context if possible, activity context only when UI operation is tied to current Activity.
Why is it important to match Context lifetime with usage?¶
View Answer
Mismatching Context lifetime with usage causes:
-
Memory leaks (Activity never garbage collected)
-
Crashes (UI operation on destroyed context)
-
Data loss (operations interrupted)
EXAMPLE - Wrong: class Manager(val context: Activity) { // Holds Activity ref fun work() { / does work / } } // Activity destroyed but Manager still referenced โ leak
CORRECT - Right: class Manager(val context: Context) { // Use generic Context fun work() { / does work / } } val manager = Manager(applicationContext) // App context
Key principle: Lifetime of Context >= Lifetime of code referencing it If code runs longer than Activity: use app context. If code tied to Activity: safe to use Activity context.
What is a memory leak in Android?¶
View Answer
A memory leak occurs when an object is no longer needed but remains referenced, preventing garbage collection.
Memory leak chain:
-
Object created
-
No longer needed (Activity destroyed, etc)
-
But still referenced by another object
-
Garbage collector can't reclaim memory
-
Memory usage grows
Consequences:
-
Out of Memory (OOM) exceptions
-
App crashes
-
Degraded performance
-
Battery drain
-
Reduced available memory for other apps
Common Android leak sources:
-
Static references to Activities
-
Long-lived objects holding Activity context
-
Unregistered listeners/callbacks
-
Handler messages with Activity context
-
Inner classes retaining Activity
How do you detect memory leaks?¶
View Answer
TOOLS:
LeakCanary library¶
-
Automatically detects leaks
-
Shows leak chain
-
Most practical tool
Android Studio Memory Profiler¶
-
Record heap allocations
-
Take heap dumps
-
Inspect object references
Logcat¶
-
Watch for OOM errors
-
Monitor memory growth
PROCESS:
-
Run app with LeakCanary/Profiler
-
Perform action multiple times (navigate, rotate, etc)
-
Look for memory growth pattern
-
Take heap dump
-
Inspect object references
-
Trace back to root reference Key: Always check that objects can be garbage collected after they're no longer needed.
What are common memory leak patterns in Android?¶
View Answer
PATTERN 1: Static Activity Reference companion object { var activity: Activity? = null // WRONG! } Fix: Don't store Activity references as static. PATTERN 2: Inner Class Holding Activity inner class MyThread : Thread() { // Holds Activity ref override fun run() { doWork() } } Fix: Use static inner class + WeakReference PATTERN 3: Unregistered Listeners registerReceiver(myReceiver, filter) // Never unregistered Fix: Always unregister in onPause/onDestroy PATTERN 4: Handler Messages handler.postDelayed({ toast.show() }, 60000) // If activity destroyed before delay ends: leak Fix: Remove messages in onDestroy PATTERN 5: Long-Lived Objects Holding Context singleton.context = this // Long-lived singleton Fix: Use app context or WeakReference
What are best practices to prevent memory leaks?¶
View Answer
-
Prefer application context over Activity context
-
Singletons, databases, shared preferences
-
Any global operation
-
Use WeakReference for Activity references
-
Long-lived objects needing Activity
-
Handler.Callback with WeakReference
-
Always unregister listeners
-
Broadcast receivers: unregister in onDestroy
-
Sensors: unregister in onStop
-
Event buses: unsubscribe in onDestroy
-
Remove handlers/callbacks before destroying
-
handler.removeCallbacks()
-
handler.removeMessages()
-
In onDestroy/onPause
-
Use ViewBinding over findViewById
-
Automatic null-out in onDestroyView
-
Prevents view references
-
Close resources properly
-
Close streams, databases, cursors
-
Try-with-resources for auto-close
-
Use LeakCanary in development
-
Catches leaks automatically
-
Part of development workflow
What is an ANR (Application Not Responding)?¶
View Answer
ANR (Application Not Responding) occurs when main thread blocks for too long, preventing UI updates or input handling.
Timeout thresholds:
-
Foreground input dispatch: ~5 seconds
-
Broadcast receiver: 10 seconds
-
Service timeouts vary by API level and state
When ANR triggered:
-
OS detects main thread unresponsive
-
OS kills app
-
System shows "waiting for..." dialog
-
User can close app or wait
Result:
-
App crash
-
Bad user experience
-
Potential uninstall
-
Play Store warnings
Common causes:
-
Blocking main thread (I/O, network, computation)
-
Heavy UI operations
-
Infinite loops
-
Deadlock situations
-
Unoptimized database queries
How do you prevent ANRs?¶
View Answer
RULE 1: Keep main thread operations quick
-
Maximum 100-200ms for user-facing operations
-
Don't do I/O, network, or heavy computation on main thread
SOLUTIONS:
Use threads for heavy work¶
Thread { heavyWork() }.start()
Use coroutines (modern approach)¶
viewModelScope.launch(Dispatchers.Default) { heavyWork() // Off main thread }
AsyncTask (legacy)¶
AsyncTask.execute { heavyWork() }
Optimize database queries¶
-
Index frequently queried columns
-
Avoid complex queries on main thread
-
Use query optimization
Lazy load data¶
-
Load progressively
-
Show skeleton/placeholder
-
Fetch full data in background
Profile with Profiler¶
-
Find slow operations
-
Optimize hotspots
Why shouldn't you do network/I/O on main thread?¶
View Answer
Main thread responsibilities:
-
Handle user input (taps, scrolls)
-
Update UI views
-
Render frames (16.67ms per frame)
-
Handle system messages
Network/I/O characteristics:
-
Unpredictable duration (100ms to seconds+)
-
Can block indefinitely
-
May fail and retry
If you block main thread with I/O:
-
Input not processed (janky/unresponsive)
-
Frames skipped (dropped frames, jank)
-
ANR timeout triggered
-
UI freezes visibly to user Solution: Off-load to background thread
-
Main thread: responsive UI
-
Background thread: I/O, network, computation
-
Post results back to main thread for UI updates Modern approach: Coroutines val data = withContext(Dispatchers.IO) { fetchData() } // Automatically switches context
What's jank and how do you measure it?¶
View Answer
JANK: Visible stutter in UI animations/scrolling. Cause: Dropping frames due to main thread blocking.
Expected frame rate:
-
60fps devices: frame every 16.67ms
-
90fps devices: frame every 11.11ms
-
120fps devices: frame every 8.33ms
If main thread busy > frame time:
-
Frame dropped
-
Animation/scroll stutters visibly
-
User sees jank
Measurement tools:
Profile GPU Rendering (dev options)¶
-
Visual graph on screen
-
Shows frame times
-
Green = good, yellow/red = jank
Android Studio Profiler¶
-
CPU, Memory, Network profiling
-
See main thread operations
FrameMetrics API¶
-
Programmatically measure frame times
-
Real user monitoring
Firebase Performance Monitoring¶
-
Production monitoring
-
Real user metrics Prevention: Keep main thread responsive (<5ms per operation).
What is Looper and how does it work?¶
View Answer
Looper: Thread mechanism for processing messages in a queue.
How it works:
-
Thread has message queue
-
Looper continuously loops through queue
-
Processes one message per iteration
-
Blocks if queue empty
-
Exits when quit() called Each thread has max 1 Looper.
Main thread always has Looper:
-
Created automatically by Android system
-
Processes UI messages, events
Background threads:
- No Looper by default
Create manually if needed¶
Thread { Looper.prepare() // ... do work ... Looper.loop() // Start looping }.start()
Why Looper matters:
-
Thread-safe message passing
-
Ordered message processing
-
Foundation for Handler/HandlerThread
-
UI updates only on main thread (main Looper)
What is Handler and how does it relate to Looper?¶
View Answer
Handler: Interface for sending messages to a Thread's message queue.
Relationship with Looper:
-
Handler posts messages to queue
-
Handler associated with Looper
-
Looper processes messages from queue
-
Handler executes callbacks on Looper's thread
Creating Handler: Handler(Looper.getMainLooper()) // Posts to main thread Handler() // Posts to current thread's Looper
Common uses: handler.post(Runnable) // Execute on handler's thread handler.postDelayed(Runnable, delay) // Delayed execution handler.sendMessage(Message) // Send message
Example - Update UI after background work: Thread { val result = doHeavyWork() mainHandler.post { updateUI(result) // Runs on main thread } }.start() Key: Handler provides thread-safe way to run code on specific thread.
What is HandlerThread and when should you use it?¶
View Answer
HandlerThread: Thread with built-in Looper for handling messages.
Benefits over plain Thread:
-
Looper automatically created and started
-
Ready to receive messages immediately
-
Clean shutdown with quit()
-
No need for manual Looper.prepare()/loop()
Creating HandlerThread: val handlerThread = HandlerThread("MyThread") handlerThread.start() val handler = Handler(handlerThread.looper)
Use cases:
-
Background image processing
-
File I/O operations
-
Database operations
-
Any repeatable background task Best practice: Combine with Handler for thread-safe updates. Handler posts work โ HandlerThread queue โ Looper processes Remember: Always call quit() when done to avoid thread leaks.
How can Handler cause memory leaks?¶
View Answer
Handler with delayed messages can leak Activity context.
Problem scenario: handler.postDelayed(Runnable { // Runnable holds implicit 'this' reference (Activity) }, 60000) // If Activity destroyed before delay ends: leak
Memory leak chain:
-
Handler post delayed message
-
Message queued for 60 seconds
-
Activity destroyed (user navigates away)
-
Message still in queue, holds Activity reference
-
Activity cannot be garbage collected
-
After 60 seconds, message executed (too late) SOLUTION 1: Use Handler with WeakReference class MyActivity : AppCompatActivity() { private val handler = Handler(Looper.getMainLooper()) private inner class MyRunnable : Runnable { override fun run() { / ... / } } } SOLUTION 2: Remove messages in onDestroy override fun onDestroy() { handler.removeCallbacks(myRunnable) super.onDestroy() } MODERN: Use coroutines instead (automatic cleanup).
What is a Service in Android?¶
View Answer
Service: Component for long-running operations in background.
Key characteristics:
-
No UI (unlike Activity)
-
Can run in background
-
Continues running even if app minimized
-
Has own lifecycle
-
Must be declared in manifest
When to use:
-
Music playback
-
Download files
-
Upload media
-
Sync data with server
-
Long-running computations
Service types:
STARTED SERVICE:
-
Runs indefinitely
-
Stopped explicitly or by system
-
No connection to caller
BOUND SERVICE:
-
Connected to caller via IPC
-
Provides interface for interaction
-
Stops when all clients unbind
FOREGROUND SERVICE:
-
Runs with visible notification
-
Higher priority, but can still be stopped by system/user
-
Used for user-aware background work Important: Services run on main thread by default. Use threads inside service for heavy work.
What's the difference between Service and Thread?¶
View Answer
THREAD:
-
Tied to single process/app
-
Dies when app killed
-
Lightweight
-
No persistence
SERVICE:
-
Can outlive Activity
-
System prioritizes keeping running
-
Has manifest declaration
-
Can be started by other apps (permission-based)
-
Less likely to be killed, but still possible
USE CASE: Heavy computation + don't need UI after? โ Thread Need background work to survive Activity destroy? โ Service
Example distinction:
-
Play music in Thread (stops when app closed)
-
Play music in Service (continues when minimized)
KEY DIFFERENCE:
Process priority:
-
Running service: Higher priority (less likely to be killed)
-
Background app with thread: Lower priority (killed first)
System Management:
-
Service: Managed by system
-
Thread: Not managed by system
What is a bound service and how do you use it?¶
View Answer
Bound Service: Service offering interface for IPC to clients.
Steps to implement:
Create Service with Binder¶
class MyBoundService : Service() { inner class LocalBinder : Binder() { fun getService() = this@MyBoundService } }
Override onBind()¶
override fun onBind(intent: Intent?): IBinder { return LocalBinder() }
Bind from Activity¶
bindService(Intent(...), connection, Context.BIND_AUTO_CREATE)
Use ServiceConnection¶
object : ServiceConnection { override fun onServiceConnected(...) { val binder = service as Binder val service = binder.getService() } }
Lifecycle:
-
onBind() when first client binds
-
onUnbind() when last client unbinds
-
Service stops if no clients bound and not started Key: Bound service tied to client lifecycle. Unbind in onDestroy/onStop to prevent leaks.
What is IntentService and when should you use it?¶
View Answer
IntentService (deprecated): Subclass of Service handling asynchronous requests.
Benefits:
-
Background thread automatically created
-
Processes one intent at a time (queue)
-
Auto-stops when queue empty
-
No manual threading needed
-
Simple to use
How it works:
-
Create IntentService subclass
-
Override onHandleIntent() (runs on bg thread)
-
Send intents with startService()
-
Threading automatically handled
-
Auto-stops after processing
Example: class DownloadService : IntentService("Download") { override fun onHandleIntent(intent: Intent?) { // Background thread, won't block UI val file = downloadFile() // Network call OK } }
Problems (Android 8.0+):
-
Background execution restrictions
-
Use WorkManager instead
MODERN APPROACH: Use WorkManager for background work (better lifecycle management, persistence, backoff policy). Legacy but useful for understanding architecture.
What is a Broadcast Receiver?¶
View Answer
Broadcast Receiver: Component for receiving system/app messages.
Characteristics:
-
Listens for broadcast intents
-
Executes onReceive() when broadcast received
-
Can run even if app not open
-
No UI capability
-
Must be declared in manifest or registered
System broadcasts:
-
ACTION_BATTERY_LOW
-
ACTION_CONNECTIVITY_CHANGE
-
ACTION_BOOT_COMPLETED
-
ACTION_PACKAGE_INSTALLED
Types of receivers:
STATIC (Manifest-declared):
-
Declared in AndroidManifest.xml
-
System can start app just to deliver broadcast
DYNAMIC (Runtime-registered):
-
Registered in code via registerReceiver()
-
Active only while app running
-
Better for app-specific broadcasts
Example: class MyReceiver : BroadcastReceiver() { override fun onReceive(context, intent) { // Process broadcast } } Use cases: Battery level, connectivity, SMS, time changes.
How do you register broadcast receivers securely?¶
View Answer
DYNAMIC REGISTRATION (Recommended):
Register in onCreate/onStart¶
registerReceiver(myReceiver, IntentFilter(ACTION))
Unregister in onDestroy/onStop¶
unregisterReceiver(myReceiver)
STATIC REGISTRATION (Manifest):
SECURITY BEST PRACTICES:
- Use specific intent filters (not catch-all) Only catch broadcasts you need
Check sender permissions¶
In onReceive(), verify sender is trusted
Require permissions for safety¶
android:permission="my.permission"
Limit receiving broadcasts¶
Use specific actions, not generic patterns
Android 8.0+ restrictions¶
-
Static receivers limited (battery optimization)
-
Use dynamic registration
-
Or use JobScheduler/WorkManager
Always unregister dynamic receivers¶
Prevents memory leaks and battery drain
What are ordered and sticky broadcasts?¶
View Answer
NORMAL BROADCAST:
-
Sent to all matching receivers simultaneously
-
No guaranteed order
-
Receivers cannot affect each other
ORDERED BROADCAST:
-
Delivered to receivers one at a time
-
In priority order
-
Each receiver can modify result or abort
-
Only next receiver gets modified data
-
Use: sendOrderedBroadcast()
STICKY BROADCAST:
-
Deprecated and removed in Android 5.0+
-
Previously: Last broadcast value retained
-
New receivers got last value on register
-
Caused security/memory issues
Example ordered broadcast: sendOrderedBroadcast(intent, null, resultReceiver, handler, resultCode, resultData, resultExtras)
Receiver can:
-
setResultCode(newCode)
-
setResultData(newData)
-
abortBroadcast() // Stop delivery to others
Use cases:
-
NORMAL: Multiple unrelated receivers
-
ORDERED: Chain of responsibility pattern
-
System broadcasts all normal Best practice: Prefer explicit app-scoped communication (shared ViewModel/Flow or explicit broadcasts). LocalBroadcastManager is deprecated.
What is the Android permission model?¶
View Answer
ANDROID 5.0 (API 21) and below:
-
All permissions granted at install time
-
User reviews app's permission list pre-install
-
Cannot revoke individual permissions
ANDROID 6.0+ (Runtime permissions):
-
Permissions granted at runtime
-
Dangerous permissions require explicit user request
-
User can revoke permissions anytime
-
App handles permission denials gracefully
Permission categories:
NORMAL PERMISSIONS (auto-granted):
-
ACCESS_NETWORK_STATE
-
INTERNET
-
Safe permissions, no user prompt
-
Declared only in manifest
DANGEROUS PERMISSIONS (runtime):
-
CAMERA, MICROPHONE
-
READ_CONTACTS, WRITE_CALENDAR
-
ACCESS_FINE_LOCATION
-
Require explicit user request via dialog
SIGNATURE PERMISSIONS:
- Only apps signed with same cert
CUSTOM PERMISSIONS:
- App-defined permissions Best practice: Request permissions only when needed (ask on usage).
How do you implement runtime permissions?¶
View Answer
STEPS:
Declare in manifest¶
Check permission at runtime¶
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { // Request permission } else { // Use camera }
Request permission¶
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 100)
Handle response¶
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array
MODERN APPROACH (Activity Result Contract): val cameraPermission = registerForActivityResult( ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) useCamera() else showDeniedMessage() } cameraPermission.launch(CAMERA) Always handle permission denials gracefully.
What are permission groups and how do they work?¶
View Answer
Permission Groups: Dangerous permissions organized by functional groups. System shows one prompt per group.
Example groups: CALENDAR: READ_CALENDAR, WRITE_CALENDAR CAMERA: CAMERA CONTACTS: READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS LOCATION: ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION MICROPHONE: RECORD_AUDIO PHONE: PROCESS_OUTGOING_CALLS, READ_PHONE_STATE, etc. SENSORS: BODY_SENSORS SMS: READ_SMS, SEND_SMS, RECEIVE_SMS, etc. STORAGE: READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE PHOTOS: READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO
How it works:
-
User grants CALENDAR group permission
-
System may group prompts for user clarity
-
Grant decisions are per permission and can vary by Android version Important: Each permission still must be declared in manifest.
Best practice:
-
Request permissions on-demand (when first needed)
-
Request least required permissions
-
Explain why permission needed (context)
-
Handle denials gracefully (skip feature)
What is AndroidManifest.xml and what does it contain?¶
View Answer
AndroidManifest.xml: Main configuration file for Android app.
Contains:
PACKAGE NAME¶
package="com.example.myapp"
COMPONENTS¶
-
Activities
-
Services
-
Broadcast receivers
-
Content providers
PERMISSIONS¶
-
Required permissions
-
Custom permissions
FEATURES¶
-
Hardware features (camera, GPS)
-
Software features
VERSION INFO¶
-
versionCode, versionName
-
targetSdkVersion, minSdkVersion
APPLICATION METADATA¶
-
App icon, label, theme
-
Backup agent
-
Hardware acceleration flags
INTENT FILTERS¶
-
Which implicit intents components handle
-
Which apps can start component
Example:
How do you declare intent filters in manifest?¶
View Answer
Intent filters declare which implicit intents a component accepts.
STRUCTURE:
COMPONENTS:
ACTION: What the component does
MATCHING RULES:
-
Implicit intent must match ALL declared filters
-
If multiple matches: chooser shown
EXAMPLE - Web link handler:
What is Binder and how does IPC work in Android?¶
View Answer
Binder: Android's Inter-Process Communication (IPC) mechanism.
Architecture:
-
Client process passes data to kernel
-
Kernel validates and transfers to server process
-
Server Binder thread processes request
-
Response sent back through kernel
Why Binder:
-
More efficient than traditional sockets
-
Thread pool management automatic
-
Reference counting built-in
-
Security: UID/PID tracking
Common uses:
-
Bound services
-
System services (LocationManager, etc)
-
Inter-app communication
-
Activity manager, package manager
AIDL (Android Interface Definition Language):
-
Define service interface
-
Compiler generates Binder stub/proxy
-
Client calls via proxy
-
Server handles in onBind()
AIDL workflow:
-
Define .aidl file
-
Build system generates code
-
Implement service
-
Bind and call methods Most apps use high-level APIs (Intent, services) but Binder is underlying mechanism.
What is Zygote and how does it create app processes?¶
View Answer
Zygote: Special system process that launches all app processes.
Problem it solves:
-
Android apps need Android runtime (VM, libraries)
-
Starting VM for each app slow
-
Zygote pre-starts VM with framework loaded
How it works:
-
Boot: Zygote process starts
-
Loads Android framework
-
Pre-initializes VM
-
Enters listening mode
App launch requested¶
-
Activity Manager requests process from Zygote
-
Zygote forks itself (fork = copy entire process)
-
Child process = new app process
App process¶
-
Inherits Zygote's loaded framework
-
Minimal initialization time
-
Ready to run app code
Benefits:
-
App launch much faster
-
Less memory per app (shared framework)
-
Faster development iteration
Process hierarchy: init โ Zygote โ ActivityManager โ App processes
Native/Java boundary:
-
Zygote is Java (runs on runtime)
-
Child processes also Java
-
System services communicate via Binder
Multiple Zygotes:
- Android 12+: Secondary Zygote for compatibility
What is the difference between ART and Dalvik?¶
View Answer
DALVIK (Android 2.2 - 4.4):
-
JIT (Just-In-Time) compilation
-
Compiles bytecode to native during app execution
-
Every run: recompiled (slower)
-
Lower app install size
-
Higher app startup time
-
Higher runtime memory usage
ART (Android 5.0+):
-
AOT (Ahead-Of-Time) compilation
-
Compiles bytecode to native at install time
-
Always runs native code (faster)
-
Larger app install size
-
Faster app startup
-
Lower runtime memory usage
-
Predictable performance
COMPARISON: Performance: ART faster (precompiled) Startup: ART much faster Battery: ART better (less CPU work) Storage: Dalvik smaller app size Install time: ART slower (compilation)
JIT vs AOT:
-
JIT: optimize while running
-
AOT: optimize before running
ART advantages:
-
More predictable performance
-
Better garbage collection
-
Improved debugging
-
Better battery life
-
Faster app startup Modern Android: Hybrid
-
Base app compiled AOT at install
-
JIT during app execution
-
Combines both benefits
What happens when you launch an app?¶
View Answer
APP STARTUP FLOW:
-
User taps app icon
-
Launcher sends Intent to ActivityManager
-
ActivityManager checks if process exists
-
No: requests process from Zygote
-
Yes: use existing process
-
Zygote fork creates new process (or uses existing)
-
New process reads app manifest
-
Process loads Application class
-
Application.onCreate() called
-
Process loads Activity
-
Activity lifecycle starts: onCreate โ onStart โ onResume
-
Activity rendered on screen
OPTIMIZATION OPPORTUNITIES:
App startup time stages¶
-
Cold start: No process exists (slowest)
-
Warm start: Process exists but Activity not (medium)
-
Hot start: Activity in memory but paused (fastest)
Optimize for cold start¶
-
Lazy load modules
-
Don't block Application.onCreate()
-
Defer non-essential initialization
-
Use StartupManager library
-
Profiling: Use startup profiler
Improve perceived performance¶
-
Show splash screen
-
Load skeleton/placeholder
-
Progressive content loading
System captures startup metrics:
-
first frame (visible)
-
fully drawn (interactive) Modern approach: Activity Result API, lazy Fragment initialization.
Why is RecyclerView more efficient than ListView?¶
View Answer
LISTVIEW DRAWBACKS:
-
Creates View for each list item
-
Never destroys views when scrolled off
-
List of 100 items = 100 Views in memory
-
Heavy GC pressure
-
Janky scrolling
RECYCLERVIEW IMPROVEMENTS:
-
Reuses View objects (recycled pools)
-
Only creates Views for visible items
-
Off-screen views returned to pool
-
Pool size = ~10-15 views
-
Original 100 items but only ~10 views created
How recycling works:
-
View scrolls off screen
-
Adapter called with holder+position
-
onBindViewHolder updates data
-
View repositioned with new content
-
No new View created
ARCHITECTURE:
-
RecyclerView: Container
-
Adapter: Binds data to views
-
ViewHolder: Holds view references (pattern)
-
LayoutManager: Positions views
-
ItemAnimator: Animations
Key efficiencies:
-
View reuse (memory efficient)
-
Predictable scroll performance
-
Animations support
-
Multiple layout types
-
Item decoration/spacing
Best practices:
-
Use ViewBinding in ViewHolder
-
Avoid heavy operations in onBindViewHolder
-
Load images asynchronously
-
Use DiffUtil for updates
How does Android render UI frames?¶
View Answer
RENDERING PIPELINE (per frame):
MEASURE¶
-
Layout system calculates view dimensions
-
ViewGroup measures children
-
Recursive: child measures own children
LAYOUT¶
-
System positions views using measured sizes
-
ViewGroup places children at x,y coordinates
DRAW¶
-
Canvas API draws views
-
Each View.onDraw() paints content
-
Composited into single frame buffer
COMPOSITE¶
-
Hardware accelerator combines layers
-
Handles transparency, transformations
DISPLAY¶
-
GPU sends frame to display
-
Vsync synchronized (60/90/120 fps)
TIMING BUDGET (60 fps):
-
16.67 milliseconds per frame
-
Measure/Layout/Draw/Composite: <16.67ms
-
Miss budget: Frame dropped, jank visible
OPTIMIZATION:
Reduce View hierarchy¶
-
Fewer views = faster measure/layout
-
Use merge, include, ViewStub
Use ViewHolder pattern¶
- Avoid repeated findViewById
Avoid layout thrashing¶
-
Don't measure in layout
-
Batch layout updates
Use hardware acceleration¶
-
Enabled by default
-
Use Layer types for animations Profiling: GPU rendering debug, Layout Inspector.
What are the different storage options in Android?¶
View Answer
SHARED PREFERENCES¶
-
Key-value store
-
SharedPreferences.getSharedPreferences()
-
Best for small data (settings, flags)
-
Lightweight
-
NOT encrypted
INTERNAL STORAGE¶
-
App-private directory
-
Deleted when app uninstalled
-
Default choice for app data
-
Secure (not accessible by other apps)
EXTERNAL STORAGE¶
-
Public folders (/DCIM, /Pictures, /Documents)
-
Shared with other apps
-
Requires permissions
-
May not exist on all devices
DATABASE (SQLite)¶
-
Structured data
-
Room library (ORM)
-
Relational queries
-
Good for complex data
CONTENT PROVIDERS¶
-
Expose data to other apps
-
Standardized data access interface
-
Used for contacts, photos, etc
CACHE¶
-
context.getCacheDir()
-
Temporary data
-
Can be cleared by system
-
Don't expect data persistence
Rules:
-
Small settings: SharedPreferences
-
Complex data: Room Database
-
Cross-app: ContentProvider
-
Temporary: Cache
-
Media: External storage Best practice: Use Room for structured data.
What is a Task and back stack in Android?¶
View Answer
TASK: Collection of activities arranged in backstack.
Each task has:
-
One backstack
-
Activities from multiple apps possible
-
Identified by taskId
-
Shown in recents BACKSTACK: LIFO (Last-In-First-Out) structure
-
Activities arranged in order launched
-
Current activity at top
-
User press back: removes top activity
EXAMPLE BACKSTACK (top to bottom): [Activity D] โ Current [Activity C] [Activity B] [Activity A] โ Home/root
User presses back: [Activity D] removed โ [Activity C] shown
FLAGS AFFECTING BACKSTACK:
FLAG_ACTIVITY_NEW_TASK:
- New activity starts in new task
FLAG_ACTIVITY_CLEAR_TOP:
-
Removes all activities above target
-
Target becomes top
FLAG_ACTIVITY_SINGLE_TOP:
-
If activity at top: calls onNewIntent()
-
Doesn't create duplicate
MULTI-APP TASKS:
-
TaskX contains ActivityA(App1), ActivityB(App2)
-
Back press: goes to App1 or App2
-
Task spans apps
Best practice:
-
Use Navigation Component (handles backstack)
-
Understand task management
-
Use flags appropriately
What happens to app state when process is killed?¶
View Answer
PROCESS DEATH: OS kills app process to free memory (no warning).
BEFORE KILL (chance to save):
-
onSaveInstanceState() called
-
Stored in Bundle
-
Persisted by system
DURING KILL:
-
Process terminated
-
No cleanup callbacks possible
-
In-memory state lost
-
No warning given
ON USER RETURN:
If Activity saved state¶
-
OS recreates process
-
onCreate(savedInstanceState) called
-
App can restore Bundle data
If no saved state¶
-
Activity restarted fresh
-
UI reset
DATA LOSS:
-
In-memory variables lost
-
Unsaved changes lost
-
ViewModels destroyed
-
Connections closed
PRESERVATION STRATEGIES:
savedInstanceState¶
-
Light data (Bundles, primitives)
-
~100KB limit
-
Configuration changes + process death
ViewModel¶
-
Survives configuration changes
-
Lost on process death
-
Use with savedInstanceState
Database¶
-
Persistent storage
-
Survives everything
-
Use for critical data
Preferences¶
-
Settings persistence
-
Survives everything Best practice: ViewModel + Room Database + savedInstanceState for robust state management.
How does multitasking affect activity lifecycle?¶
View Answer
MULTITASKING SCENARIOS:
SPLIT SCREEN¶
-
Two apps visible simultaneously
-
Both in foreground state
-
Both receive onResume()
-
Top activity interactive, other visible
PICTURE-IN-PICTURE¶
-
App playing video in small window
-
onPause() called (not visible)
-
Continues running
-
User can interact with background
APP SWITCHING (Recents)¶
-
Swipe from other app
-
Current Activity: onPause โ onStop
-
Switched app: onStart โ onResume
-
Current app not destroyed (still in memory)
LIFECYCLE IMPACT:
Old app (going background): onPause() โ onStop()
New app (coming foreground): onStart() (if first time) or onStart() โ onResume() (if was paused)
IMPORTANT:
-
onPause() called for BOTH foreground apps
-
Only top activity "interactive"
-
Other visible but not interactive
CONSEQUENCES:
-
Stop CPU-heavy operations in onPause()
-
Resume operations in onResume()
-
Don't assume onStop() means background
-
May be visible but not interactive Example: Video player in split screen
-
visible but onPause() called
-
Should pause playback in onPause()
-
Resume in onResume()
Explain Android i18n correctness - plurals, gender-neutral text, number formatting, and locale handling¶
View Answer
Android i18n goes beyond string translation โ plurals, date/number formats, and text direction all change based on locale and must be handled by platform APIs, not hardcoded logic.
In interviews, cover:
-
plurals: use
resources with quantity strings (zero, one, two, few, many, other); getQuantityString(R.plurals.x, count, count) โ never concatenate count + " items" in code -
number formatting: NumberFormat.getInstance(locale).format(n) or NumberCompat; never hardcode commas or periods as decimal separators โ they are locale-specific
-
date formatting: use DateTimeFormatter with explicit locale; avoid toString() on Date/Calendar classes which use the system default locale
-
locale configuration changes: configuration change when the user switches language results in an Activity recreation; ensure ViewModels are locale-independent (store raw data, not formatted strings)
Strong answer tip:
- test by forcing locale with LocaleList.setDefault() in an Espresso test or using the ADB command: adb shell am start -a android.intent.action.MAIN --locale de_DE
Explain RTL layout support - bidirectional text, icon direction, and meaning preservation¶
View Answer
Android flips horizontal layout automatically in RTL locales when supportsRtl=true is set in the manifest, but logical mirroring of meaning (not just geometry) requires explicit design attention.
In interviews, cover:
-
enable RTL: android:supportsRtl="true" in
; use start/end instead of left/right in layout attributes; layout direction flips automatically -
icons: directional icons (back arrow, forward arrow, skip) must be mirrored in RTL; non-directional icons (play, settings, share) must not; use android:autoMirrored="true" in SVG drawables for directional ones or provide explicit -ldrtl resources
-
bidirectional text (BiDi): a string with mixed Arabic and English characters; rely on BidiFormatter or android:textDirection="locale" rather than hardcoding LTR/RTL text direction on TextViews
-
padding/margin: Modifier.padding(start=16.dp) in Compose, paddingStart in Views โ these automatically flip in RTL
Strong answer tip:
- test with: adb shell settings put global debug.force_rtl 1 (developer option) to force RTL on any locale without actually changing device language
Explain accessibility at scale - audit strategy, semantics coverage, and keyboard/D-pad navigation¶
View Answer
Accessibility at scale requires systematic auditing, not ad-hoc fixes; semantic coverage, focus order, and touch target size are the three most common failure classes.
In interviews, cover:
-
semantic coverage: every interactive element needs a contentDescription or labelFor; group related elements with mergeDescendants; use Role (Button, Checkbox, Image) so TalkBack announces the correct interaction model
-
touch target size: Material spec recommends 48ร48dp minimum; Modifier.minimumInteractiveComponentSize() enforces this in Compose; small tap targets fail WCAG 2.5.5
-
focus order: keyboard and D-pad navigation must follow a logical reading order; customise with Modifier.focusProperties { next = focusRef } in Compose or android:nextFocusDown in Views
-
audit tooling: Accessibility Scanner app, Android Studio Layout Inspector accessibility tab, and automated checks via UiAutomator with AccessibilityNodeInfoCompat
Strong answer tip:
- integrate automated accessibility checks into your UI test suite using AccessibilityChecks.enable() in Espresso โ this runs Google's accessibility test framework on every test run and catches regressions before code review