← Back to list

The “Sticky” Trap: Surviving Null Intents and Pending Queues in Android

Mastering state restoration, null intents, and lifecycle-aware background tasks in modern Android development.

Android Expert in Venture · 2026-04-19 16:25 · 1 claps · 3.5 min read paywalled
#android-development #kotlin #android-jetpack #android-service #background-processing
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

The “Sticky” Trap: Surviving Null Intents and Pending Queues in Android

The “Sticky” Trap: Surviving Null Intents and Pending Queues in Android

The “Sticky” Trap: Surviving Null Intents and Pending Queues in Android

Not a Medium Member? “Read For Free”

Ever had your Android app crash in the background while the user wasn’t even using it? Welcome to the “Sticky Trap.” While START_STICKY is a fundamental tool for background execution, it hides a massive catch: when the system brings your service back from the dead, it might not bring the original Intent data with it.

The Real-World Scenario

Imagine you are building a File Sync Service.

  1. The user starts a large 500MB sync.
  2. The user switches to a heavy game, pushing your app into the background.
  3. The Android Low Memory Killer (LMK) kills your process to reclaim RAM.
  4. Minutes later, the system recreates your service because you returned START_STICKY.
  5. The Crash: Your code tries to read intent.getStringExtra("FILE_PATH"). But since the intent is null, your app throws a NullPointerException and crashes again.

Understanding the Service Restart Flow

The Precision Nuance: When is the Intent Null?

The system may call onStartCommand() with a null intent when there are no pending start requests queued by the system.

Note on Queued Intents: These are not PendingIntents (the API), but rather internal start requests queued by the system. If multiple components called startService() before the crash, the OS will deliver those queued Intents one by one.

Expert Note: Even with START_REDELIVER_INTENT, only the last Intent is guaranteed to be redelivered. A senior developer must architect for both: the "fresh" Intent and the "restarted" null Intent.

The Modern Reality: Android 8+ Restrictions

Since Android 8.0 (API 26), the system has imposed strict background execution limits to preserve battery life.

  • The startForegroundService() Requirement: On Android 8.0+, you must often use startForegroundService() and call startForeground() within a short window; otherwise, the system may stop the service and trigger an ANR.
  • The IllegalStateException: Starting a background service from the background targeting API 26+ can throw this exception if the app isn’t in an exempt state.
  • The Solution: Use a Foreground Service for immediate tasks or migrate to WorkManager for deferrable background syncs.

A Robust Kotlin Example: The “Crash-Resistant” Service

This implementation uses stopSelf(startId) to ensure the service only shuts down when the latest work is complete and handles the null intent gracefully.

class RobustSyncService : Service() {

    private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 1. Handle the "Sticky Trap": System restarted with no pending requests
        if (intent == null) {
            handleSystemRestart(startId)
            return START_STICKY
        }

        val filePath = intent.getStringExtra("FILE_PATH")

        if (filePath != null) {
            startSync(filePath, startId)
        } else {
            // Stop only if this startId is the most recent one delivered
            stopSelf(startId) 
        }

        return START_STICKY
    }

    private fun startSync(path: String, startId: Int) {
        serviceScope.launch {
            try {
                // Perform async sync logic...
            } finally {
                // Ensure stopSelf(startId) is called AFTER the async work
                stopSelf(startId)
            }
        }
    }

    private fun handleSystemRestart(startId: Int) {
        Log.d("SyncService", "Restarted with NULL intent. Checking Room DB...")
        // Re-hydrate state from a persistent source (Room/DataStore)
        if (!hasPendingSyncTasksInDb()) {
            stopSelf(startId)
        }
    }

    override fun onBind(intent: Intent?): IBinder? = null

    override fun onDestroy() {
        super.onDestroy()
        serviceScope.cancel() 
    }
}

Common Mistakes to Avoid

  • Missing Null Checks: Assuming intent or intent.extras will always exist in a START_STICKY service.
  • Relying on Statics: Expecting static variables to survive process death. Use Room or DataStore instead.
  • Global stopSelf(): Using stopSelf() instead of stopSelf(startId), which can accidentally kill the service while a second intent is still being processed.
  • Ignoring API 26+ Rules: Forgetting that background services are heavily restricted in modern Android.

Choosing Your Survival Strategy

Choosing Your Survival Strategy

Choosing Your Survival Strategy

Key Takeaway

If your service depends on Intent data, START_STICKY alone is not a safety net. Design for process death: Assume the Intent will be null, persist your state in a database, and always use startId for clean shutdowns.

💬 Questions for the Viewers

  • Are you handling the null intent in your onStartCommand, or is your app a "Sticky Trap" waiting to happen?
  • In your current architecture, would START_REDELIVER_INTENT be a safer choice than START_STICKY?

📘 Master Your Next Technical Interview

Since Java is the foundation of Android development, mastering DSA is essential. I highly recommend “Mastering Data Structures & Algorithms in Java”. It’s a focused roadmap covering 100+ coding challenges to help you ace your technical rounds.

A message from our Founder

Hey, Sunil here. I wanted to take a moment to thank you for reading until the end and for being a part of this community. Did you know that our team run these publications as a volunteer effort to over 3.5m monthly readers? We don’t receive any funding, we do this to support the community.

If you want to show some love, please take a moment to follow me on LinkedIn, TikTok, Instagram. You can also subscribe to our weekly newsletter. And before you go, don’t forget to clap and follow the writer️!


메타데이터
post_id
daeaf46ad4c0
slug
the-sticky-trap-surviving-null-intents-and-pending-queues-in-android-daeaf46ad4c0
url
https://blog.venturemagazine.net/the-sticky-trap-surviving-null-intents-and-pending-queues-in-android-daeaf46ad4c0
canonical_url
https://blog.venturemagazine.net/the-sticky-trap-surviving-null-intents-and-pending-queues-in-android-daeaf46ad4c0
author_url
https://medium.com/@sivavishnu0705
status
ok
fetched_at
2026-06-21 07:44:09