Exécuter des threads dans ListenableWorker

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Dans certains cas, vous devrez peut-être fournir une stratégie de thread personnalisée. Par exemple, vous devrez peut-être gérer une opération asynchrone basée sur le rappel. Dans ce cas, vous ne pouvez pas simplement vous appuyer sur un Worker, car il ne peut pas effectuer le travail de blocage. WorkManager accepte ce cas d'utilisation avec ListenableWorker. ListenableWorker est l'API de nœud de calcul la plus basique ; Worker, CoroutineWorker et RxWorker sont tous dérivés de cette classe. Un ListenableWorker indique uniquement quand le travail doit démarrer et s'arrêter, laissant les threads sous votre entière responsabilité. Le signal de début du travail est appelé sur le thread principal. Il est donc très important que vous accédiez manuellement au thread d'arrière-plan de votre choix.

La méthode abstraite ListenableWorker.startWork() renvoie un ListenableFuture du Result. Un ListenableFuture est une interface légère : il s'agit d'un Future qui fournit une fonctionnalité permettant d'associer des écouteurs et de propager des exceptions. Dans la méthode startWork, vous devez renvoyer ListenableFuture, que vous définirez avec le Result de l'opération une fois celle-ci terminée. Vous pouvez créer des instances ListenableFuture de deux manières :

  1. Si vous employez Guava, utilisez ListeningExecutorService.
  2. Sinon, incluez councurrent-futures dans votre fichier Gradle et utilisez CallbackToFutureAdapter.

Si vous souhaitez exécuter une tâche basée sur un rappel asynchrone, procédez comme suit :

Kotlin

class CallbackWorker(
        context: Context,
        params: WorkerParameters
) : ListenableWorker(context, params) {
    override fun startWork(): ListenableFuture<Result> {
        return CallbackToFutureAdapter.getFuture { completer ->
            val callback = object : Callback {
                var successes = 0

                override fun onFailure(call: Call, e: IOException) {
                    completer.setException(e)
                }

                override fun onResponse(call: Call, response: Response) {
                    successes++
                    if (successes == 100) {
                        completer.set(Result.success())
                    }
                }
            }

            repeat(100) {
                downloadAsynchronously("https://example.com", callback)
            }

            callback
        }
    }
}

Java

public class CallbackWorker extends ListenableWorker {

    public CallbackWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        return CallbackToFutureAdapter.getFuture(completer -> {
            Callback callback = new Callback() {
                int successes = 0;

                @Override
                public void onFailure(Call call, IOException e) {
                    completer.setException(e);
                }

                @Override
                public void onResponse(Call call, Response response) {
                    successes++;
                    if (successes == 100) {
                        completer.set(Result.success());
                    }
                }
            };

            for (int i = 0; i < 100; i++) {
                downloadAsynchronously("https://www.example.com", callback);
            }
            return callback;
        });
    }
}

Que se passe-t-il en cas d'arrêt de votre tâche ? Le ListenableFuture d'un ListenableWorker est toujours annulé lorsque la tâche doit s'arrêter. Avec CallbackToFutureAdapter, il vous suffit d'ajouter un écouteur d'annulation comme suit :

Kotlin

class CallbackWorker(
        context: Context,
        params: WorkerParameters
) : ListenableWorker(context, params) {
    override fun startWork(): ListenableFuture<Result> {
        return CallbackToFutureAdapter.getFuture { completer ->
            val callback = object : Callback {
                var successes = 0

                override fun onFailure(call: Call, e: IOException) {
                    completer.setException(e)
                }

                override fun onResponse(call: Call, response: Response) {
                    ++successes
                    if (successes == 100) {
                        completer.set(Result.success())
                    }
                }
            }

 completer.addCancellationListener(cancelDownloadsRunnable, executor)

            repeat(100) {
                downloadAsynchronously("https://example.com", callback)
            }

            callback
        }
    }
}

Java

public class CallbackWorker extends ListenableWorker {

    public CallbackWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        return CallbackToFutureAdapter.getFuture(completer -> {
            Callback callback = new Callback() {
                int successes = 0;

                @Override
                public void onFailure(Call call, IOException e) {
                    completer.setException(e);
                }

                @Override
                public void onResponse(Call call, Response response) {
                    ++successes;
                    if (successes == 100) {
                        completer.set(Result.success());
                    }
                }
            };

            completer.addCancellationListener(cancelDownloadsRunnable, executor);

            for (int i = 0; i < 100; ++i) {
                downloadAsynchronously("https://www.example.com", callback);
            }
            return callback;
        });
    }
}

Exécuter un ListenableWorker dans un processus différent

Vous pouvez également lier un nœud de calcul à un processus spécifique à l'aide de RemoteListenableWorker, une implémentation de ListenableWorker.

RemoteListenableWorker se lie à un processus spécifique avec deux arguments supplémentaires que vous fournissez dans les données d'entrée lors de la création de la requête de tâche : ARGUMENT_CLASS_NAME et ARGUMENT_PACKAGE_NAME.

L'exemple suivant illustre la création d'une requête de tâche liée à un processus spécifique :

Kotlin

val PACKAGE_NAME = "com.example.background.multiprocess"

val serviceName = RemoteWorkerService::class.java.name
val componentName = ComponentName(PACKAGE_NAME, serviceName)

val data: Data = Data.Builder()
   .putString(ARGUMENT_PACKAGE_NAME, componentName.packageName)
   .putString(ARGUMENT_CLASS_NAME, componentName.className)
   .build()

return OneTimeWorkRequest.Builder(ExampleRemoteListenableWorker::class.java)
   .setInputData(data)
   .build()

Java

String PACKAGE_NAME = "com.example.background.multiprocess";

String serviceName = RemoteWorkerService.class.getName();
ComponentName componentName = new ComponentName(PACKAGE_NAME, serviceName);

Data data = new Data.Builder()
        .putString(ARGUMENT_PACKAGE_NAME, componentName.getPackageName())
        .putString(ARGUMENT_CLASS_NAME, componentName.getClassName())
        .build();

return new OneTimeWorkRequest.Builder(ExampleRemoteListenableWorker.class)
        .setInputData(data)
        .build();

Pour chaque RemoteWorkerService, vous devez également ajouter une définition de service dans votre fichier AndroidManifest.xml :

<manifest ... >
    <service
            android:name="androidx.work.multiprocess.RemoteWorkerService"
            android:exported="false"
            android:process=":worker1" />

        <service
            android:name=".RemoteWorkerService2"
            android:exported="false"
            android:process=":worker2" />
    ...
</manifest>

Exemples