Google a récemment annoncé un nouveau composant d'architecture WorkManager. Cela facilite la planification du travail synchrone en implémentant doWork()
dans la classe Worker
, mais que se passe-t-il si je souhaite effectuer un travail asynchrone en arrière-plan? Par exemple, je souhaite effectuer un appel de service réseau à l’aide de Retrofit. Je sais que je peux faire une requête réseau synchrone, mais cela bloquerait le thread et donnerait l'impression d'être dans l'erreur. Existe-t-il une solution ou n'est-elle simplement pas prise en charge pour le moment?
Per Documents WorkManager :
Par défaut, WorkManager exécute ses opérations sur un fil d’arrière-plan. Si vous exécutez déjà sur un thread en arrière-plan et avez besoin d'appels synchrones (bloquants) à WorkManager, utilisez synchronous () pour accéder à ces méthodes.
Par conséquent, si vous n'utilisez pas synchronous()
, vous pouvez effectuer en toute sécurité des appels réseau synchronisés à partir de doWork()
. C'est également une meilleure approche du point de vue de la conception car les rappels sont compliqués.
Cela dit, si vous souhaitez réellement déclencher des travaux asynchrones à partir de doWork()
, vous devez suspendre le thread d'exécution et le reprendre à la fin du travail asynchrone à l'aide du mécanisme wait/notify
(ou d'un autre mécanisme de gestion de thread, par exemple Semaphore
). Pas quelque chose que je recommanderais dans la plupart des cas.
En remarque, WorkManager est en alpha très tôt.
J'ai utilisé un compte à rebours et ai attendu que cela atteigne 0, ce qui ne se produira que lorsque le rappel asynchrone l'a mis à jour. Voir ce code:
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];
}
FYI il y a maintenant ListenableWorker , qui est conçu pour être asynchrone.
Edit: Voici quelques extraits d’utilisation. J'ai découpé de gros morceaux de code que je pense ne sont pas illustratifs, alors il y a de bonnes chances qu'il y ait une ou deux erreurs mineures ici.
Ceci concerne une tâche qui prend une photoKey en chaîne, récupère les métadonnées d'un serveur, effectue un certain travail de compression, puis télécharge la photo compressée. Cela se produit hors du fil principal. Voici comment nous envoyons la demande de travail:
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);
}
Et dans 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;
}
}
Si vous parlez de travail asynchrone, vous pouvez déplacer votre travail dans RxJava Observables/Singles.
Il existe un ensemble d'opérateurs tels que .blockingGet()
ou .blockingFirst()
Qui transforme Observable<T>
en blocage T
Worker
fonctionne sur le thread d'arrière-plan, alors ne vous inquiétez pas pour NetworkOnMainThreadException
.
J'ai utilisé BlockingQueue
, qui simplifie la synchronisation des threads et le résultat, vous aurez besoin d'un seul objet
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
}
}
N'oubliez pas non plus de libérer des ressources si votre travailleur a été arrêté, c'est le principal avantage par rapport à .blockingGet()
, car vous pouvez maintenant annuler correctement et librement votre tâche Rx.
override fun onStopped(cancelled: Boolean) {
disposable.dispose()
}