Асинхронный рабочий стол в Android WorkManager

Google недавно анонсировал новый компонент архитектуры WorkManager. Это позволяет легко планировать синхронную работу, реализуя doWork() в классе Worker, но что если я захочу выполнить некоторую асинхронную работу в фоновом режиме? Например, я хочу сделать вызов сетевой службы с помощью Retrofit. Я знаю, что могу сделать синхронный сетевой запрос, но он блокирует поток и просто чувствует себя не так. Есть ли какое-то решение для этого или просто сейчас не поддерживается?

Ответ 1

В документах WorkManager:

По умолчанию WorkManager выполняет свои операции в фоновом потоке. Если вы уже работаете в фоновом потоке и нуждаетесь в синхронных (блокирующих) вызовах WorkManager, используйте synchronous() для доступа к таким методам.

Поэтому, если вы не используете synchronous(), вы можете безопасно выполнять вызовы с синхронизацией из doWork(). Это также лучший подход с точки зрения дизайна, потому что обратные вызовы беспорядочны.

Тем не менее, если вы действительно хотите doWork() async-задания из doWork(), вам нужно приостановить поток выполнения и возобновить его при завершении работы async с использованием механизма wait/notify (или какого-либо другого механизма управления потоками, например, Semaphore). Не то, что я бы рекомендовал в большинстве случаев.

В качестве дополнительной заметки WorkManager находится в очень ранней альте.

Ответ 2

Я использовал countdownlatch и ждал, пока это достигнет 0, что произойдет только после того, как асинхронный обратный вызов обновит его. Смотрите этот код:

public WorkerResult doWork() {

        final WorkerResult[] result = {WorkerResult.RETRY};
        CountDownLatch countDownLatch = new CountDownLatch(1);
        FirebaseFirestore db = FirebaseFirestore.getInstance();

        db.collection("collection").whereEqualTo("this","that").get().addOnCompleteListener(task -> {
            if(task.isSuccessful()) {
                task.getResult().getDocuments().get(0).getReference().update("field", "value")
                        .addOnCompleteListener(task2 -> {
                            if (task2.isSuccessful()) {
                                result[0] = WorkerResult.SUCCESS;
                            } else {
                                result[0] = WorkerResult.RETRY;
                            }
                            countDownLatch.countDown();
                        });
            } else {
                result[0] = WorkerResult.RETRY;
                countDownLatch.countDown();
            }
        });

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return result[0];

    }

Ответ 3

К вашему сведению, теперь есть ListenableWorker, который спроектирован как асинхронный.

Изменение: Вот некоторые примеры использования примера. Я вырезал большие куски кода, которые, на мой взгляд, не являются иллюстративными, поэтому есть большая вероятность, что здесь есть небольшая ошибка или две.

Это для задачи, которая принимает String photoKey, получает метаданные с сервера, выполняет некоторую работу по сжатию, а затем загружает сжатую фотографию. Это происходит вне основного потока. Вот как мы отправляем рабочий запрос:

private void compressAndUploadFile(final String photoKey) {
    Data inputData = new Data.Builder()
            .putString(UploadWorker.ARG_PHOTO_KEY, photoKey)
            .build();
    Constraints constraints = new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build();
    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(UploadWorker.class)
            .setInputData(inputData)
            .setConstraints(constraints)
            .build();
    WorkManager.getInstance().enqueue(request);
}

И в UploadWorker:

public class UploadWorker extends ListenableWorker {
    private static final String TAG = "UploadWorker";
    public static final String ARG_PHOTO_KEY = "photo-key";

    private String mPhotoKey;

    /**
     * @param appContext   The application {@link Context}
     * @param workerParams Parameters to setup the internal state of this worker
     */
    public UploadWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
        mPhotoKey = workerParams.getInputData().getString(ARG_PHOTO_KEY);
    }

    @NonNull
    @Override
    public ListenableFuture<Payload> onStartWork() {
        SettableFuture<Payload> future = SettableFuture.create();
        Photo photo = getPhotoMetadataFromServer(mPhotoKey).addOnCompleteListener(task -> {
            if (!task.isSuccessful()) {
                Log.e(TAG, "Failed to retrieve photo metadata", task.getException());
                future.setException(task.getException());
                return;
            }
            MyPhotoType photo = task.getResult();
            File file = photo.getFile();
            Log.d(TAG, "Compressing " + photo);
            MyImageUtil.compressImage(file, MyConstants.photoUploadConfig).addOnCompleteListener(compressionTask -> {
                if (!compressionTask.isSuccessful()) {
                    Log.e(TAG, "Could not parse " + photo + " as an image.", compressionTask.getException());
                    future.set(new Payload(Result.FAILURE));
                    return;
                }
                byte[] imageData = compressionTask.getResult();
                Log.d(TAG, "Done compressing " + photo);
                UploadUtil.uploadToServer(photo, imageData);
                future.set(new Payload(Result.SUCCESS));
            });
        });
        return future;
    }
}

Ответ 4

Если вы говорите об асинхронном задании, вы можете переместить свою работу в RxJava Observables/Singles.

Существует набор операторов, таких как .blockingGet() или .blockingFirst() который преобразует Observable<T> в блокировку T

Worker выполняет фоновый поток, поэтому не беспокойтесь о NetworkOnMainThreadException.

Ответ 5

Я использовал BlockingQueue, который упрощает синхронизацию потоков и передает результат, вам понадобится только один объект

private var disposable = Disposables.disposed()


override fun doWork(): Result {
    val result = LinkedBlockingQueue<Result>()

    disposable = completable.subscribe(
            { result.put(Result.SUCCESS) },
            { result.put(Result.RETRY) }
    )

    return try {
        result.take()
    } catch (e: InterruptedException) {
        Result.RETRY
    }
}

Также не забудьте освободить ресурсы, если ваш Worker был остановлен, это основное преимущество над .blockingGet() поскольку теперь вы можете правильно освободить свою Rx-задачу.

override fun onStopped(cancelled: Boolean) {
    disposable.dispose()
}