Since I need to perform work asynchronously in WorkManager, I need to use the ListenableWorker
, which by default runs on the main (UI) thread. Since this work c
If you want to continuously (i.e., less than every 60 seconds), you absolutely should be using a foreground service and not WorkManager, which is for, as per the documentation:
deferrable, asynchronous tasks
And not something that needs to run near continously.
However, if you do proceed to incorrectly use WorkManager, you'd want to keep the following in mind:
Your custom doWork
method runs on the main thread because as per the setExecutor() documentation:
An Executor for running Workers
Specifically, only the Worker subclass of ListenableWorker
runs on a background thread provided by the Executor
- not your ListenableWorker
implementation.
As per the ListenableWorker.startWork() documentation:
This method is called on the main thread.
Because you're using ListenableWorker
, your startWork
method is being called on the main thread, as expected. Since you call your own doWork()
method on the same thread, you'll still be on the main thread.
In your case, you don't need to care about what thread you're on and you don't need any Executor
since it doesn't matter what thread you call getLastLocation()
on.
Instead, you need to only call set
on your ResolvableFuture
when you actually have a result - i.e., in the onSuccess()
or onFailure
callbacks. This is the signal to WorkManager
that you're actually done with your work:
public class LocationWorker extends ListenableWorker {
static final String UNIQUE_WORK_NAME = "LocationWorker";
static final String KEY_NEW_LOCATION = "new_location";
private static final String TAG = "LocationWorker";
private ResolvableFuture<Result> mFuture;
private LocationCallback mLocationCallback;
public LocationWorker(@NonNull final Context appContext, @NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
@NonNull
@Override
public ListenableFuture<Result> startWork() {
Log.d(TAG, "Starting work " + getId());
mFuture = ResolvableFuture.create();
Utils.setRequestingLocationUpdates(getApplicationContext(), true);
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
LocationUtils.getInstance(getApplicationContext()).removeLocationUpdates(this);
Location location = locationResult.getLastLocation();
Log.d(TAG, "Work " + getId() + " returned: " + location);
// Rescheduling work
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(LocationWorker.class).setInitialDelay(10, TimeUnit.SECONDS).build();
WorkManager.getInstance().enqueueUniqueWork(LocationWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, request);
Log.d(TAG, "Rescheduling work. New ID: " + request.getId());
// Always set the result as the last operation
mFuture.set(Result.success(Utils.getOutputData(location)));
}
};
LocationUtils.getInstance(getApplicationContext()).requestSingleUpdate(mLocationCallback, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
LocationUtils.getInstance(getApplicationContext()).removeLocationUpdates(mLocationCallback);
Utils.setRequestingLocationUpdates(getApplicationContext(), false);
WorkManager.getInstance().cancelUniqueWork(UNIQUE_WORK_NAME);
mFuture.set(Result.failure());
}
});
return mFuture;
}
}
Well - As the other answers mentioned, if you want to do frequent work (like every 60 sec) you should use foreground service.
Regardless, I'd use coroutines to get out of the main thread. Like:
runBlocking(Dispatchers.Default) {
//If you want to run blocking your code
}
Or using the GlobalScope Launch
GlobalScope.launch {
//Work in a separated thread (running not blocking)
}
Here is a practical example of getting a location using ListenableWorker and setting up the listeners out of the main thread.
https://github.com/febaisi/ListenableWorkerExample/blob/master/app/src/main/java/com/febaisi/listenableworkerexample/data/LocationListenableWorker.kt#L28