Поскольку мне нужно выполнять асинхронную работу в WorkManager, мне нужно использовать ListenableWorker
, который по умолчанию выполняется в основном потоке (UI). Поскольку эта работа может быть длительной обработкой задач, которые могут заморозить интерфейс, я хотел выполнить ее в фоновом потоке. В видео Работа с WorkManager (Android Dev Summit '18) инженер Google показал, как вручную настроить WorkManager для запуска работ на пользовательском Executor
, поэтому я следовал его указаниям:
1) Отключить инициализатор WorkManager по умолчанию в AndroidManifest:
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="com.example.myapp.workmanager-init"
tools:node="remove" />
2) В Application.onCreate инициализируйте WorkManager с пользовательской конфигурацией, которая в моем случае выглядит следующим образом:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Configuration configuration = new Configuration.Builder().setExecutor(Executors.newSingleThreadExecutor()).build();
WorkManager.initialize(this, configuration);
}
}
Теперь мой фактический ListenableWorker
такой:
@NonNull
@Override
public ListenableFuture<Result> startWork() {
Log.d(TAG, "Work started.");
mFuture = ResolvableFuture.create();
Result result = doWork();
mFuture.set(result);
return mFuture;
}
private Result doWork() {
Log.d(TAG, "isMainThread? " + isMainThread());
mFusedLocationProviderClient.getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
if (location != null) {
// Since I still don't know how to communicate with the UI, I will just log the location
Log.d(TAG, "Last location: " + location);
return Result.success();
} else {
return Result.failure();
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
e.printStackTrace();
return Result.failure();
}
});
}
private boolean isMainThread() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
Почему метод isMainThread()
возвращает true, хотя я указал Executor
WorkManager, который следует использовать в качестве нового фонового потока, и как я могу на самом деле запустить этот фрагмент работы в фоновом потоке?
РЕДАКТИРОВАТЬ: ListenableWorker
с необходимостью CountDownLatch
.
Поскольку мне нужно перепланировать работу каждый раз, когда она выполняется успешно (обходной путь для минимального интервала 15 минут для PeriodicWorkRequest
), я должен сделать это после того, как предыдущая часть работы вернула успех, в противном случае у меня странное поведение. Это необходимо, потому что, по-видимому, ExistingWorkPolicy.APPEND
не работает должным образом.
Вариант использования - запросить обновления местоположения с высокой точностью с довольно частым интервалом (5-10 с), даже в фоновом режиме. Включается и выключается с помощью SMS, даже когда приложение не запущено (но не принудительно остановлено), или с помощью кнопки (это университетский проект).
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;
private CountDownLatch mLatch;
private Context mContext;
public LocationWorker(@NonNull final Context appContext, @NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
mContext = appContext;
Utils.setRequestingLocationUpdates(mContext, true);
mLatch = new CountDownLatch(1);
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
LocationUtils.getInstance(mContext).removeLocationUpdates(this);
Location location = locationResult.getLastLocation();
Log.d(TAG, "Work " + getId() + " returned: " + location);
mFuture.set(Result.success(Utils.getOutputData(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());
// Relase lock
mLatch.countDown();
}
};
}
@NonNull
@Override
public ListenableFuture<Result> startWork() {
Log.d(TAG, "Starting work " + getId());
mFuture = ResolvableFuture.create();
LocationUtils.getInstance(mContext).requestSingleUpdate(mLocationCallback, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
LocationUtils.getInstance(mContext).removeLocationUpdates(mLocationCallback);
Utils.setRequestingLocationUpdates(mContext, false);
WorkManager.getInstance().cancelUniqueWork(UNIQUE_WORK_NAME);
mFuture.set(Result.failure());
// Relase lock
mLatch.countDown();
}
});
try {
mLatch.await(5L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return mFuture;
}
}