I am not sure if the topic is related to detached apps only but I am running one so here it goes. I am working on an application that uses location updates in the background. While testing the feature on Android I found that a crash happens after several minutes in the background.
Test Device: Pixel 3XL
OS Version: Android 9 (Pie)
Stacktrace:
02-26 17:02:11.431 15639 15639 E AndroidRuntime: FATAL EXCEPTION: main
02-26 17:02:11.431 15639 15639 E AndroidRuntime: Process: [...], PID: 15639
02-26 17:02:11.431 15639 15639 E AndroidRuntime: java.lang.RuntimeException: Unable to start receiver expo.modules.taskManager.TaskBroadcastReceiver: java.lang.IllegalStateException: Apps may not schedule more than 100 distinct jobs
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.app.ActivityThread.handleReceiver(ActivityThread.java:3426)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.app.ActivityThread.access$1200(ActivityThread.java:200)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1667)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.os.Looper.loop(Looper.java:193)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6718)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: Caused by: java.lang.IllegalStateException: Apps may not schedule more than 100 distinct jobs
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.os.Parcel.createException(Parcel.java:1958)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.os.Parcel.readException(Parcel.java:1918)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.os.Parcel.readException(Parcel.java:1868)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.app.job.IJobScheduler$Stub$Proxy.schedule(IJobScheduler.java:184)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.app.JobSchedulerImpl.schedule(JobSchedulerImpl.java:44)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at expo.modules.taskManager.TaskManagerUtils.scheduleJob(TaskManagerUtils.java:59)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at expo.modules.taskManager.TaskManagerUtils.scheduleJob(TaskManagerUtils.java:71)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at expo.modules.location.taskConsumers.LocationTaskConsumer.didReceiveBroadcast(LocationTaskConsumer.java:104)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at expo.modules.taskManager.TaskService.handleIntent(TaskService.java:283)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at expo.modules.taskManager.TaskBroadcastReceiver.onReceive(TaskBroadcastReceiver.java:15)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.app.ActivityThread.handleReceiver(ActivityThread.java:3417)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: ... 8 more
02-26 17:02:11.431 15639 15639 E AndroidRuntime: Caused by: android.os.RemoteException: Remote stack trace:
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at com.android.server.job.JobSchedulerService.scheduleAsPackage(JobSchedulerService.java:871)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at com.android.server.job.JobSchedulerService$JobSchedulerStub.schedule(JobSchedulerService.java:2568)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.app.job.IJobScheduler$Stub.onTransact(IJobScheduler.java:60)
02-26 17:02:11.431 15639 15639 E AndroidRuntime: at android.os.Binder.execTransact(Binder.java:731)
02-26 17:02:11.431 15639 15639 E AndroidRuntime:
Config options:
// https://docs.expo.io/versions/v32.0.0/sdk/location/
export const OPTIONS = {
accuracy: Location.Accuracy.Highest, // This consumes a lot more power
timeInterval: 500, // Minimum time to wait between each update in milliseconds.
distanceInterval: 1, // Updates only when location has changed by at least this distance in meters.
showsBackgroundLocationIndicator: false, // Hides blue bar on top
};
The application utilises redux-saga
for handling location stuff. The location saga is started with the JS app and looks like this:
const locationSagas = function* locationSagas(): Saga<any> {
while (true) {
try {
// Stop location updates in case of launching the app after killing it during the last workout
Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
// wait for observation initialization action
const { type: startType } = startObservingLocationAction();
yield put(logger('INFO', `Waiting for ${startType} request`));
yield take(startType);
const isTaskRegistered = yield !TaskManager.isTaskRegisteredAsync(LOCATION_TASK_NAME);
if (isTaskRegistered) {
throw new Error('Location task is not registered');
}
store.dispatch(logger('INFO', 'Start location updates'));
Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, OPTIONS);
yield put(logger('INFO', 'Observation of location data started'));
// wait for cancellation action
const { type: stopType } = stopObservingLocationAction();
yield put(logger('INFO', `Waiting for ${stopType} request`));
yield take(stopType);
} catch (e) {
yield put(logger('ERROR', e.message, 'sagas/location#observeLocationData'));
} finally {
yield put(logger('INFO', 'Stop location updates'));
Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
}
}
};
I am not sure if the stopLocationUpdatesAsync
at the beginning of the saga (which I suppose may be restarted if the TaskManager restarts the app) does mess up with the JobScheduler or not.