-
Notifications
You must be signed in to change notification settings - Fork 241
Android ANR "Context.startForegroundService() did not then call Service.startForeground()"
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:
The BackgroundGeolocation SDK registers listeners, known as PendingIntent
s, 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.
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
.
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 yourMainActivity
class. - Right-click on the containing folder and select New -> Java Class:
- Name the class
MainApplication
and press[ENTER]
. - Open your
AndroidManifest
file and change the attributeandroid:name
to reference your customMainApplication
class.
<application
...
android:name=".MainApplication"
- Now go back to your newly created
MainApplication
class and paste the following BELOW the top linepackage 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.
In a terminal window, observe adb logcat
using the filter *:S StrictMode:V
:
$ adb logcat *:S StrictMode:V
You can also chain multiple logs TAG
s 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.