Task Manager Job Doesn't Get Fired: Location stops updating after 5-10 minutes on Android (when using Foreground Service)
See original GitHub issueSummary
I’m testing Expo 42 with an ejected app (bare workflow). My app watches the current device location in foreground (when the app is open) and background (when the screen is turned off) using Android’s Foreground Service functionality, by permanently displaying a notification icon in the status bar.
Generally, it works as expected. However, after about 5 to 10 minutes, Android seems to put the device to “sleep” and that’s when my task (TaskManager.defineTask
) doesn’t get fired periodically anymore. Instead, I only receive batch updates every couple of minutes, but not in real-time anymore, and thus useless for a real-time app relying on accurate location info. This is reproducible on both a real device as well as the Emulator running API 30.
I have spent about half a day debugging through Expo sources and finally found the culprit: At some point, when Android puts the device into a deeper sleep (aka “dozing”) after 5 minutes or so, the Expo Task Manager stops firing. Jobs are still scheduled but they aren’t run immediately anymore but batched and run only after a long delay of several minutes. I’m talking about this code in expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerUtils.java
:
JobInfo jobInfo = createJobInfo(context, task, newJobId, data);
jobScheduler.schedule(jobInfo);
The job will simply not fire right away and subsequent jobs will get merged without getting fired either. These deferred jobs will apparently only run every 10 to 15 minutes or so.
I finally got a workaround going by increasing the priority of the job by using setImportantWhileForeground
. I don’t really know the implications for other parts of Expo or on the battery usage, but for background location, this seems to work as expected now. Here’s a patch for TaskManagerUtils.java
that I’m applying in my local repo using patch-package:
diff --git a/node_modules/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerUtils.java b/node_modules/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerUtils.java
index b40f2d1..7d47fd7 100644
--- a/node_modules/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerUtils.java
+++ b/node_modules/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerUtils.java
@@ -8,6 +8,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.PersistableBundle;
@@ -195,11 +196,16 @@ public class TaskManagerUtils implements TaskManagerUtilsInterface {
}
private JobInfo createJobInfo(int jobId, ComponentName jobService, PersistableBundle extras) {
- return new JobInfo.Builder(jobId, jobService)
- .setExtras(extras)
- .setMinimumLatency(0)
- .setOverrideDeadline(DEFAULT_OVERRIDE_DEADLINE)
- .build();
+ JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobService)
+ .setExtras(extras);
+ if (Build.VERSION.SDK_INT < 28) {
+ jobBuilder.setMinimumLatency(1)
+ .setOverrideDeadline(DEFAULT_OVERRIDE_DEADLINE);
+ } else {
+ // FIXME: This method was deprecated in API level 31. Use setExpedited(boolean) instead.
+ jobBuilder.setImportantWhileForeground(true);
+ }
+ return jobBuilder.build();
}
private JobInfo createJobInfo(Context context, TaskInterface task, int jobId, List<PersistableBundle> data) {
This change ensures that Android keeps firing the job on time even if the device is sleeping.
Setting this to true indicates that this job is important while the scheduling app is in the foreground or on the temporary whitelist for background restrictions. This means that the system will relax doze restrictions on this job during this time. Apps should use this flag only for short jobs that are essential for the app to function properly in the foreground. Note that once the scheduling app is no longer whitelisted from background restrictions and in the background, or the job failed due to unsatisfied constraints, this job should be expected to behave like other jobs without this flag.
I’m also setting setMinimumLatency(1)
as I’ve read somewhere that 0
might not always work as expected on some older Android versions when used in combination with setOverrideDeadline()
. I didn’t bother testing it though 🤷 . Also, note that setOverrideDeadline
has no effect if the device is dozing. Its value is simply ignored. But in any case, a deadline of 1 minute wouldn’t really be sufficient for real-time location updates anyway.
tl;dr.: Seems that the Expo Task Manager will stop running jobs such as the location updates once a device goes sleeping. The above patch avoids this by setting setImportantWhileForeground
and making sure tasks keep firing.
Managed or bare workflow? If you have ios/
or android/
directories in your project, the answer is bare!
bare
What platform(s) does this occur on?
Android
SDK Version (managed workflow only)
No response
Environment
Expo CLI 4.10.0 environment info:
System:
OS: macOS 11.5.2
Shell: 5.8 - /bin/zsh
Binaries:
Node: 14.16.1 - ~/.nvm/versions/node/v14.16.1/bin/node
npm: 7.20.3 - ~/.nvm/versions/node/v14.16.1/bin/npm
Watchman: 2021.06.07.00 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.10.2 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: iOS 14.5, DriverKit 20.4, macOS 11.3, tvOS 14.5, watchOS 7.4
Android SDK:
API Levels: 29, 30
Build Tools: 29.0.2, 29.0.3, 30.0.2, 30.0.3
System Images: android-30 | Google APIs Intel x86 Atom, android-30 | Google Play Intel x86 Atom
IDEs:
Android Studio: 2020.3 AI-203.7717.56.2031.7583922
Xcode: 12.5.1/12E507 - /usr/bin/xcodebuild
npmPackages:
expo: ^42.0.3 => 42.0.3
react: 17.0.1 => 17.0.1
react-dom: 17.0.1 => 17.0.1
react-native: ^0.64.1 => 0.64.2
npmGlobalPackages:
expo-cli: 4.10.0
Expo Workflow: bare
Reproducible demo or steps to reproduce from a blank project
None
Issue Analytics
- State:
- Created 2 years ago
- Reactions:5
- Comments:12 (2 by maintainers)
Hi, is there any solution or deadline for this problem to be fixed in the managed workflow? its really critical for our app and we really dont want to eject 😦
This is so sad. Is there any way I can be of help to get this fixed in the codebase (implementing, testing, although I would only partly know what I’m doing there), so we can use the functionality also in managed workflows, or at least in bare workflows without patching stuff by hand?