Skip to content

Android ANR "Context.startForegroundService() did not then call Service.startForeground()"

Chris Scott edited this page Apr 17, 2023 · 15 revisions

The ANR ("Activity Not Responding") Context.startForegroundService() did not then call Service.startForeground() is a well-known complaint among Android Developers who use foreground-services (as this Background Geolocation SDK does.

Fatal Exception: android.app.RemoteServiceException
Context.startForegroundService() did not then call Service.startForeground()

When an Android foreground-service is launched, the OS provides exactly 5 seconds for an app to promote a Service to the foreground by calling .startForeground(). From the Android API docs:

Screenshot 2023-04-17 at 10 28 24 AM

The BackgroundGeolocation SDK registers listeners, known as PendingIntents, with the OS that are capable of re-launching a terminated app in the background (eg: ActivityRecognition API, Geofencing API). When this occurs (as noted above) the plugin's foreground-service has 5 seconds to call .startForeground(). The problem here is that a Service.start method is called on the Main Thread — if there's anything in your app that is also consuming time on the Main Thread, the plugin's Service.start method will not be called in time, leading to this ANR "Context.startForegroundService() did not then call Service.startForeground()".

Think of an app launching as an airplane taking off at an Airport: All the tasks that run on the Main Thread when your app is launched are like the passengers waiting in line at the security check. The Background Geolocation SDK is one of those passengers and it isn't the first one in line. If any passenger in front of Background Geolocation takes more time than usual during the security scan (eg: forgets coins in his pocket, forgets to take off their watch, doesn't take boots off) more time is consumed. If five seconds elapses before Background Geolocation makes its way through security (Calling Service.startForeground()), the OS reports this ANR Context.startForegroundService() did not then call Service.startForeground().

An example of "consuming too much time on the Main Thread" is doing stuff like executing a long-running SQL query (SQL queries should be run in a background-thread) or opening files from "disk" (Android's SharedPreferences loads its contents from an XML file on "disk").

Over the years of developing the Background Geolocation SDK, extensive effort has been put into ensuring the SDK performs all its long-running tasks in background-threads, in order not to block the Main Thread. Any further reports of this ANR are due to your own code or other plugins used in your app.

Diagnosing Main Thread Consumption with StrictMode

To diagnose where code in your app is consuming too much time on the Main Thread, you can use Android StrictMode. The /example app in this repo implements StrictMode.

Once you implement StrictMode, you can observe $ adb logcat *:S StrictMode:V to observe StrictMode violations, which look like this:

D StrictMode: StrictMode policy violation; ~duration=58 ms: android.os.strictmode.DiskReadViolation
D StrictMode: 	at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1658)
D StrictMode: 	at libcore.io.BlockGuardOs.access(BlockGuardOs.java:74)
D StrictMode: 	at libcore.io.ForwardingOs.access(ForwardingOs.java:128)
D StrictMode: 	at android.app.ActivityThread$AndroidOs.access(ActivityThread.java:7749)
D StrictMode: 	at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:281)
D StrictMode: 	at java.io.File.exists(File.java:813)
D StrictMode: 	at android.app.ContextImpl.getDataDir(ContextImpl.java:2962)
D StrictMode: 	at android.app.ContextImpl.getPreferencesDir(ContextImpl.java:704)
D StrictMode: 	at android.app.ContextImpl.getSharedPreferencesPath(ContextImpl.java:931)
D StrictMode: 	at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:553)
D StrictMode: 	at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:217)
D StrictMode: 	at io.flutter.plugins.sharedpreferences.MethodCallHandlerImpl.<init>(MethodCallHandlerImpl.java:54)
D StrictMode: 	at io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin.setupChannel(SharedPreferencesPlugin.java:36)
D StrictMode: 	at io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin.onAttachedToEngine(SharedPreferencesPlugin.java:26)
D StrictMode: 	at io.flutter.embedding.engine.FlutterEngineConnectionRegistry.add(FlutterEngineConnectionRegistry.java:144)
D StrictMode: 	at io.flutter.plugins.GeneratedPluginRegistrant.registerWith(GeneratedPluginRegistrant.java:29)

Here we see StrictMode policy violation; ~duration=58 ms where the plugin named io.flutter.plugins.sharedpreferences consumed 58ms on the Main Thread. This 58ms is not very large but eats away at the "5 seconds" that the OS provides for foreground-services to call Service.startForeground.

Implementing StrictMode in your app:

To implement StrictMode, your app needs to declare a custom Application override class. By default, Flutter apps do not declare one, so you must create one (if your app doesn't already have one in your android/app/src/main/java/com.your.namespace/Application.java).

If your app does not already contain an Application override, open your android folder in your IDE (I use Android Studio) and create one:

  • Expand your java folder to find your MainActivity class.
  • Right-click on the containing folder and select New -> Java Class:
Screenshot 2023-04-17 at 11 17 01 AM
  • Name the class MainApplication and press [ENTER].
  • Open your AndroidManifest file and change the attribute android:name to reference your custom MainApplication class.
<application
    ...
    android:name=".MainApplication"
  • Now go back to your newly created MainApplication class and paste the following BELOW the top line package your.package.name:
import android.os.StrictMode;

import io.flutter.app.FlutterApplication;

public class MainApplication extends FlutterApplication {
    @Override
    public void onCreate() {
        ////
        // Implement Strict mode.  Should be disabled on RELEASE builds.
        // 
        //
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectDiskReads()
                .detectDiskWrites()
                .detectAll()
                .penaltyLog()
                .build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .detectLeakedSqlLiteObjects()
                .detectLeakedClosableObjects()
                .penaltyLog()
                .penaltyDeath()
                .build());

        super.onCreate();
    }
}

Your app is now configured to log StrictMode violations.

Observing StrictMode violations.

In a terminal window, observe adb logcat using the filter *:S StrictMode:V:

$ adb logcat *:S StrictMode:V

You can also chain multiple logs TAGs together, like so:

$ adb logcat *:S StrictMode:V TSLocationManager:V flutter:V

This will show logs from the BackgroundGeolocation SDK and Flutter print messages in addition to StrictMode violations.

You will want to observe StrictMode violations while your app is launched from a terminated state.