Android App Components: Activity, Fragment, Service, BroadcastReceiver & ContentProvider
The 4 Android app components — fundamentals that every Android interview covers
Open interactive version (quiz + challenge)Real-world analogy
What is it?
Android's 4 application components (Activity, Service, BroadcastReceiver, ContentProvider) are the entry points through which the system and other apps can interact with your application. Each has a distinct purpose and lifecycle. Fragment is not a component but an architectural building block. Modern Android development minimizes direct component use — Fragments for screens, WorkManager for background work, FileProvider for sharing.
Real-world relevance
In a school management platform: Activities handle the main entry point and deep links from notifications. Fragments implement each screen (student list, grade book, attendance). A Foreground Service with a notification plays audio during online classes. BroadcastReceivers listen for network changes to trigger sync. FileProvider shares grade report PDFs with other apps.
Key points
- The 4 app components — Activity, Service, BroadcastReceiver, and ContentProvider are the 4 Android components that must be declared in AndroidManifest.xml. The Android system can start each component independently. Fragment is NOT a top-level component — it requires a host Activity.
- Activity lifecycle — onCreate → onStart → onResume (visible and interactive) → onPause → onStop → onDestroy. onPause: called when losing focus (another Activity in front). onStop: Activity is no longer visible. onDestroy: called before Activity is destroyed (or by finish()). onRestart: from stopped back to started.
- Activity vs Fragment debate — Modern Android recommendation (2019+): Single Activity architecture with multiple Fragments or Compose screens. Activity handles deep linking, permissions, and system integration. Fragments/Compose manage screen content. Benefits: shared ViewModel, simpler navigation, better transition animations.
- Fragment lifecycle complexity — Fragments have TWO lifecycles: Fragment lifecycle and View lifecycle. viewLifecycleOwner (the View) is created in onCreateView and destroyed in onDestroyView — use for UI observations. Fragment lifecycle persists through view recreation (e.g., in backstack). Always use viewLifecycleOwner for view-related observers.
- Service types — Started Service: runs in background until stopped (IntentService is deprecated — use WorkManager or coroutines). Bound Service: clients bind to it and interact via IBinder interface. Foreground Service: shows a persistent notification, higher priority, survives low memory. In 2025: prefer WorkManager over Services for deferrable work.
- Foreground Service requirements — Since Android 8 (API 26), background execution is limited — apps cannot start background Services freely. Foreground Service requires calling startForeground() within 5 seconds of starting and showing a notification. Required for: music playback, navigation, real-time location tracking.
- BroadcastReceiver — explicit vs implicit — Explicit broadcasts target a specific component (your app): Intent(context, MyReceiver::class.java). Implicit broadcasts declare an action (other apps or system can handle): action = Intent.ACTION_BOOT_COMPLETED. Since Android 8: most implicit broadcasts cannot be registered in Manifest — must use Context.registerReceiver() at runtime.
- BroadcastReceiver execution limits — onReceive() runs on the MAIN thread and must complete within ~10 seconds (triggers ANR). For async work in onReceive(), use goAsync() to get a PendingResult, do async work, then call pendingResult.finish(). BroadcastReceiver has no lifecycle — it dies after onReceive() returns.
- ContentProvider for data sharing — ContentProvider exposes structured data to other apps via a content URI (content://com.example.provider/table). It's the only way to safely share data between apps. Used by: MediaStore (photos), ContactsProvider (contacts), FileProvider (sharing files via Intent). Rarely built from scratch — use FileProvider for file sharing.
- AndroidManifest registration — All 4 components must be declared in AndroidManifest.xml. Activity: . Service: . Receiver: (for static registration). Provider: .
- Pending Intent — PendingIntent wraps an Intent with your app's identity — allows another app or the system to perform the action on your behalf later (notification click, alarm, widget button). Must specify mutability: PendingIntent.FLAG_IMMUTABLE (preferred) or FLAG_MUTABLE.
- WorkManager vs Service — WorkManager (Jetpack): for deferrable, guaranteed background work that survives app kill (sync, upload). Foreground Service: for immediate, user-visible long-running work (music, navigation). AlarmManager: for exact time-triggered work (calendar reminders). Use WorkManager by default for background tasks.
Code example
// Activity — Single Activity architecture entry point
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// NavHostFragment handles all screen navigation
}
// Handle deep links / results from other apps
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleDeepLink(intent)
}
}
// Fragment — correctly using viewLifecycleOwner
class StudentListFragment : Fragment() {
private val viewModel: StudentViewModel by viewModels()
private var _binding: FragmentStudentListBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentStudentListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Use viewLifecycleOwner — NOT 'this' — for view-related observations
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.students.collect { renderStudents(it) }
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // prevent memory leak
}
}
// Foreground Service — audio playback for online class
class ClassAudioService : Service() {
private var mediaPlayer: MediaPlayer? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = buildNotification("Live Class in Progress")
startForeground(NOTIFICATION_ID, notification) // required within 5s
when (intent?.action) {
ACTION_PLAY -> startAudio(intent.getStringExtra("stream_url"))
ACTION_STOP -> { stopAudio(); stopSelf() }
}
return START_STICKY // restart if killed by system
}
override fun onBind(intent: Intent?): IBinder? = null // not a bound service
}
// BroadcastReceiver — runtime registered for network changes
class NetworkChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// onReceive runs on MAIN thread — keep it fast
val isConnected = isNetworkAvailable(context)
if (isConnected) {
WorkManager.getInstance(context).enqueue(
OneTimeWorkRequestBuilder<SyncWorker>().build()
)
}
}
}
// FileProvider — sharing grade report PDF
fun shareGradeReport(file: File) {
val uri = FileProvider.getUriForFile(
requireContext(),
"${requireContext().packageName}.fileprovider",
file
)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "application/pdf"
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, "Share Report"))
}Line-by-line walkthrough
- 1. @AndroidEntryPoint MainActivity — Hilt entry point; handles deep links and system integration; NavHostFragment does all navigation
- 2. private val viewModel: StudentViewModel by viewModels() — Hilt/AAC creates the ViewModel; same instance across config changes
- 3. viewLifecycleOwner.lifecycleScope — tied to Fragment's VIEW lifecycle, not Fragment lifecycle; avoids null binding crashes
- 4. _binding = null in onDestroyView() — view is destroyed when Fragment is backstacked; clearing prevents memory leak from view reference
- 5. startForeground(NOTIFICATION_ID, notification) — must be called within 5 seconds of Service start on Android 8+; shows persistent notification
- 6. return START_STICKY — system restarts Service with null Intent if killed; appropriate for ongoing audio/location services
- 7. override fun onBind(): IBinder? = null — returns null since this is a started Service, not a bound Service
- 8. WorkManager.enqueue in BroadcastReceiver — don't do long async work directly; enqueue WorkManager task and return immediately
- 9. FileProvider.getUriForFile(...) — converts File to content:// URI; FLAG_GRANT_READ_URI_PERMISSION gives receiving app temporary access
Spot the bug
class SyncReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Sync takes 30 seconds
GlobalScope.launch {
repository.syncAllData() // bug 1
}
// onReceive returns — receiver is dead
}
}
class StudentListFragment : Fragment() {
override fun onViewCreated(view: View, saved: Bundle?) {
// Using 'this' instead of viewLifecycleOwner
viewModel.students.observe(this) { students -> // bug 2
binding.recyclerView.adapter = StudentAdapter(students)
}
}
}Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- Android Application Components (developer.android.com)
- Activity Lifecycle (developer.android.com)
- Services Overview (developer.android.com)
- BroadcastReceiver Overview (developer.android.com)
- FileProvider (developer.android.com)