Why Android 8+ Blocks startService in Background and How startForegroundService Avoids Crashes
This article explains the Android 8.1+ restriction that prevents background apps from using startService, details the internal flow that leads to a Not allowed to start service crash, shows how startForegroundService works with a mandatory foreground notification timeout, and provides code‑level analysis of the ANR and crash “bombs”, UID idle handling, and practical mitigation strategies.
1. Background
On Android 8.1 and later, a background application that calls startService triggers a crash because the system no longer permits background service starts.
2. Cause of the Crash
Google introduced these limits to improve app security and battery consumption. The recommended alternative is startForegroundService, which must call startForeground within a short timeout (5 seconds on Android 8.1, 10 seconds on Android 8–12, configurable up to 30 seconds on Android 13). Failing to do so results in an ANR and then a forced crash.
3. Questions Addressed
How does Android enforce the restriction on startService for third‑party apps?
What are the differences between startService and startForegroundService?
Why does startForegroundService cause ANR/crash when the foreground notification is not posted in time?
How is the timeout calculated?
Does calling startService or startForegroundService from the UI thread cause ANR?
4. Detailed Analysis of startService Crash
4.1 Service Start Flow (Android 11 example)
The full start flow involves ContextImpl.startService → ActivityManagerService.startService → ActiveServices.startServiceLocked → AMS.getAppStartModeLocked. The key decision point is the allowed value returned by getAppStartModeLocked. If the app is in the background and not on a whitelist, allowed is not APP_START_MODE_NORMAL, causing the system to return a ComponentName with package name “?” and the framework throws a SecurityException with the message “Not allowed to start service”.
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) {
try {
validateServiceIntent(service);
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
getOpPackageName(), getAttributionTag(), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException("Not allowed to start service " + service + " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException("Unable to start service " + service + ": " + cn.getClassName());
} else if (cn.getPackageName().equals("?")) {
throw new IllegalStateException("Not allowed to start service " + service + ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}The three conditions that lead to the “?” result are: startRequested is false when the service has not yet been created. allowed is not APP_START_MODE_NORMAL. fgRequired is false (i.e., the call is not a foreground start).
When all three are true, the system throws the “Not allowed to start service” exception.
5. startForegroundService Mechanics
5.1 Difference from startService
The only difference is that requireForeground is set to true. The rest of the flow is identical until the service is created.
5.2 ANR and Crash “Bombs”
When a service is started via startForegroundService, the system schedules a timeout task ( SERVICE_START_FOREGROUND_TIMEOUT) that will fire if startForeground is not called in time. The timeout is implemented in ActiveServices.sendServiceArgsLocked:
if (r.fgRequired && !r.fgWaiting) {
if (!r.isForeground) {
scheduleServiceForegroundTransitionTimeoutLocked(r);
} else {
r.fgRequired = false;
}
}The scheduling method posts a delayed message to the ActivityManagerService handler:
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
if (r.app.executingServices.size() == 0 || r.app.thread == null) return;
Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
msg.obj = r;
r.fgWaiting = true;
mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT);
}On Android 13 the default timeout is 30 seconds; earlier versions use 5–10 seconds.
If the timeout expires, the handler processes SERVICE_FOREGROUND_TIMEOUT_MSG and calls serviceForegroundTimeout:
void serviceForegroundTimeout(ServiceRecord r) {
if (!r.fgRequired || r.destroying) return;
ProcessRecord app = r.app;
if (app != null && app.isDebugging()) return;
r.fgWaiting = false;
stopServiceLocked(r);
if (app != null) {
mAm.mAnrHelper.appNotResponding(app, "Context.startForegroundService() did not then call Service.startForeground(): " + r);
}
}The stopServiceLocked method removes the timeout message and posts a crash message:
if (r.fgRequired) {
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
msg.obj = r.app;
msg.getData().putCharSequence(ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
mAm.mHandler.sendMessage(msg);
}The crash handler finally invokes crashApplication in the system process, which forwards the request to the target process and forces a crash via scheduleCrash on the process’s main thread.
6. Interaction with Process Launch
If the target service’s process is not yet running, the system first starts the process (via startProcessLocked), adds the service to a pending list, and once the process is attached, ActiveServices.attachApplicationLocked creates the service on the new process’s main thread. The timeout is already scheduled at this point, so the window for calling startForeground includes the time needed to launch the process and execute onCreate.
7. UID Idle Management
Android tracks whether an app is in the background using UidRecord.idle. When an app moves to the background, a delayed message (60 seconds) is posted; after the delay the UID is marked idle and any background services are stopped. This mechanism prevents rapid foreground‑background switches from immediately marking an app as idle.
void updateUidsLocked(ActiveUids activeUids, long nowElapsed) {
// ...
if (ActivityManager.isProcStateBackground(uidRec.getCurProcState()) && !uidRec.curWhitelist) {
if (!ActivityManager.isProcStateBackground(uidRec.setProcState) || uidRec.setWhitelist) {
uidRec.lastBackgroundTime = nowElapsed;
if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, mConstants.BACKGROUND_SETTLE_TIME);
}
}
if (uidRec.idle && !uidRec.setIdle) {
uidChange = UidRecord.CHANGE_IDLE;
becameIdle.add(uidRec);
}
} else {
if (uidRec.idle) {
uidChange = UidRecord.CHANGE_ACTIVE;
uidRec.idle = false;
}
uidRec.lastBackgroundTime = 0;
}
// ...
}8. Mitigation Strategies
Optimize
Application attachBaseContext, onCreate, and the service’s onCreate to finish quickly.
Post a lightweight foreground notification early (e.g., a placeholder) and update it later, reducing the time before startForeground is called.
When the main UI activity is in the foreground (UID idle = false), use startService instead of startForegroundService, then call startForeground after the service starts.
Start the process first via a ContentProvider or other component, then invoke startForegroundService to avoid the initial launch delay.
9. Conclusion
The Android framework blocks background startService calls to improve security and battery life. startForegroundService bypasses this block but enforces a strict timeout for posting a foreground notification. Understanding the internal flow—especially the scheduling of the ANR and crash “bombs”, the role of UidRecord.idle, and the interaction with process launch—allows developers to design services that avoid unexpected crashes and ANRs.
Ximalaya Technology Team
Official account of Ximalaya's technology team, sharing distilled technical experience and insights to grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
