Long-Running Background Tasks: A Survival Guide

The War on Background Processing

It Continues!

  • Attempts by Google to reduce battery consumption and other perceived waste
  • War ongoing since 4.4, heated up with Doze mode/app standby in 6.0
  • Casualties: apps that relied upon now-broken techniques, users of said apps

Types of Background Work

At Least, Conventional Types

  • Foreground-Triggered Work
    • Transactional (e.g., file download)
    • Ongoing (e.g., media player)
  • Background-Triggered Work
    • Periodic (e.g., scheduled server sync)
    • Push messages (e.g., C2DM/GCM/FCM)
    • System broadcasts as triggers (e.g., after app installs)

Historical Thread Recommendations

(No, That's Not "Hysterical")

  • Under 1ms? Use the main application thread
  • Over 1ms? Use any form of background thread
  • Over 1 second? Use a service
  • Over 15 seconds, or background-triggered? Wakelock, WiFi-lock, and foreground service
  • Good news! Follow this advice, and your code still works!

Foreground Foes

  • Do not want to clutter up the status bar
  • Do not want to clutter up the notification shade
  • Do not want the user to know that the service is running

Oreo Started Services

Gone in 60 Seconds

  • Limited to "several minutes"... where "several" seems be "one"
  • After that, service will be stopped, akin to stopService()
  • Does not directly terminate your process, but dramatically increases the odds that it will terminate shortly
  • Affects targetSdkVersion 26... and anyone the user chooses to blame

The Foreground Workaround

  • Simple solution: make your service be a foreground service
  • Foreground started ("unbound") services can run normally
  • Does not make the "foreground foes" happy, but... ¯\_(ツ)_/¯

No Ouroboros

A Snake Eating Its Tail, To Save You a Wikipedia Search

  • With no running services, your process is considered "cached"
  • While cached, you cannot start other services using startService()

Promises, Promises

Because You Never Lie to Android, Right?

  • Call startForegroundService() instead
  • Expectation: you will call startForeground() from the service, within the ANR timeout period
  • Works even from cached processes
  • And, if you just happen to get your work done in a few seconds, and never call startForeground()... ¯\_(ツ)_/¯

About the Foreground Notification

You Can Run, But You Can't Hide

  • Using a minimum-importance channel hides your notification... but shows a system one, indicating that you are running
  • Using any higher importance shows your own notification

Get a Job(IntentService)

  • JobIntentService, found in Support Library 26.0.0 and higher
  • On Android 7.1 and older, behaves like IntentService
  • On Android 8.0+, uses JobScheduler
  • Gives you a larger but undocumented window for work (10 minutes?)
  • Handles wakelock, akin to now-deprecated WakefulBroadcastReceiver

Implicit Broadcast Ban

Fighting Fire with More Fire

  • targetSdkVersion 26 apps cannot receive implicit broadcasts via manifest-registered receivers
  • Massive expansion of formerly uncommon limitation (e.g., ACTION_BATTERY_CHANGED)
  • Rationale: process churn
  • Select system implicit broadcasts are whitelisted, as are self-broadcasts protected by signature permissions

Triggering Work Sans Implicit Broadcasts

This Jocular Sub-Head Available for Rent, Inquire Within

  • Find another way to get this information, via polling and JobScheduler
  • Use a foreground service and a dynamic receiver
  • Keep your targetSdkVersion below 26 until you can figure out a better workaround

Questions?

https://commonsware.com/presos/androidSummit2017