This is Day 7 of my 100 Days to Senior Android Engineer series. Every 7th day is a weekly recap — a structured summary of what I covered, what surprised me, and what I'm carrying into next week.
Week 1 is done.
Six topics. Six posts. And more gaps in my "solid" understanding than I expected to find.
Here's the honest summary.
What I covered this week
| Day | Topic | Key insight |
|---|---|---|
| Day 1 | Why I started this | Knowing how vs knowing why |
| Day 2 | The 4 Android components | Each is an OS entry point, not just a class |
| Day 3 | Activity Lifecycle |
onDestroy() is not guaranteed |
| Day 4 | Fragment Lifecycle | Two lifecycles: Fragment + View |
| Day 5 | Intent, Task & Back Stack |
singleTask clears your back stack silently |
| Day 6 | Context | Lifetime mismatch = memory leak |
The 6 most important things I (re)learned
1. Android components are entry points, not just classes
When you declare an Activity, Service, BroadcastReceiver, or ContentProvider in AndroidManifest.xml, you're giving the OS a door into your app — one it can open independently, even if the rest of your app isn't running.
This reframes how you think about crashes and ANRs. When something fires unexpectedly, the first question is now: which entry point triggered this, and what are its thread and lifecycle contracts?
The one that catches people most: BroadcastReceiver.onReceive() runs on the main thread and dies immediately after the method returns. No coroutine scope. No lifecycle. Delegate to WorkManager and return.
2. onDestroy() is not a safe place to save critical state
The lifecycle diagram shows onDestroy() as a clean endpoint. The reality:
Under memory pressure, the system can kill your process without calling
onDestroy().
onPause() is the only callback guaranteed to be called before your process dies. If you're saving anything critical — a draft, a form input, a transaction — it belongs in onPause() or better yet, in SavedStateHandle.
// The modern answer for state that must survive both
// configuration changes AND process death:
class FormViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
var draftInput: String
get() = savedStateHandle["draft"] ?: ""
set(value) { savedStateHandle["draft"] = value }
}
3. Fragment has TWO lifecycles — and mixing them up leaks memory
A Fragment's View is destroyed and recreated every time the user navigates away and back on the back stack. The Fragment instance stays alive. This means:
- Observers scoped to the Fragment (
this) keep firing even when the View is gone -
bindingreferences become stale but don't throw until you touch them
The fix is one word: viewLifecycleOwner.
// ❌ Observer stays alive after view is destroyed
viewModel.state.observe(this) { updateUI(it) }
// ✅ Observer cancelled when view is destroyed
viewModel.state.observe(viewLifecycleOwner) { updateUI(it) }
If you write one thing differently after reading this series, make it this.
4. singleTask clears your back stack — and it's silent about it
This is the one I found most embarrassing to admit, because I've shipped it.
singleTask doesn't just prevent duplicate instances. It destroys everything above the target Activity in the back stack when it's reused. A user mid-flow taps a notification, and their navigation history is gone.
User's back stack: [Home] → [List] → [Detail] → [Settings]
Notification tap targets Detail (singleTask):
Result: [Home] → [Detail]
↑ List and Settings silently destroyed
The correct tool for notification deep links is TaskStackBuilder, which synthesizes a logical back stack instead of destroying the existing one.
5. Context lifetime mismatch is the root cause of most leaks
The rule I now use:
Match the context's lifetime to the consumer's lifetime.
- Consumer lives forever (singleton, library) →
applicationContext - Consumer is UI-bound (View, Dialog, LayoutInflater) → Activity context
Using applicationContext everywhere "to be safe" isn't safe — it silently breaks theme-dependent attributes and crashes Dialog creation. Using Activity context everywhere leaks memory into long-lived objects.
The decision is about lifetime, not about which one compiles.
6. ContentProvider initializes before Application.onCreate()
This one was a genuine surprise.
Libraries like WorkManager, Firebase, and Jetpack Startup use an empty ContentProvider to auto-initialize without requiring any code in your Application class. The ContentProvider.onCreate() runs before Application.onCreate(), giving them a guaranteed early initialization hook.
Process starts →
ContentProviders init (manifest order) →
Application.onCreate() →
Your first Activity/Service/Receiver
Next time you wonder how a library "just works" with no setup — check if it ships a ContentProvider in its manifest.
The question I couldn't answer confidently until Day 3
On Day 2, I wrote about the two-Activity transition callback order. I thought I knew it:
A.onPause() → A.onStop() → B.onCreate() → B.onStart() → B.onResume()
Wrong.
A.onPause() → B.onCreate() → B.onStart() → B.onResume() → A.onStop()
A.onStop() comes after B.onResume(). Activity B is fully visible before Activity A stops. I've known this diagram for years and never caught the ordering detail.
The practical consequence: if you hold an exclusive resource (camera, audio focus) and release it in onStop() instead of onPause(), Activity B will try to acquire it before you've released it.
What surprised me most about Week 1
I expected to fly through the fundamentals. These are basics — I've been doing Android for 8 years.
What I found instead: I had solid procedural knowledge (what to call, when to call it) but weaker causal knowledge (why the system works this way, what breaks when you violate the rules).
The gaps showed up in the edge cases:
- The precise callback order between two Activities
- What
singleTaskactually does to the back stack - The Fragment's View lifecycle being genuinely separate from the Fragment lifecycle
None of these are obscure. They're in the official documentation. I just hadn't read them carefully in a long time — and "mostly right" had been good enough.
It's not good enough for senior-level conversations.
Into Week 2
Next week: Process Death, Memory, ANR, and Main Thread Model.
These are the topics that show up in production incident post-mortems more than anywhere else. They're also the topics where the gap between "I've heard of this" and "I can diagnose this in production" is largest.
Starting Monday with Day 8: Process Death — what actually happens when Android kills your app.
One question for you
What's the Android fundamental you feel least confident explaining to a junior? Drop it in the comments — it might become a future post.
Follow the full series to get all 100 days.
← Day 6: Context in Android — The Wrong One Will Leak Your Entire Activity
Top comments (0)