Exécuter des threads dans ListenableWorker

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. WorkManager prend en charge 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 la tâche doit démarrer et s'arrêter, et laisse le threading à vous. Le signal de début de tâche est appelé sur le thread principal. Il est donc très important d'accéder manuellement au thread d'arrière-plan de votre choix.

La méthode abstraite ListenableWorker.startWork() renvoie un ListenableFuture de Result. Un ListenableFuture est une interface légère, c'est-à-dire un Future qui fournit une fonctionnalité permettant d'associer des écouteurs et de propager des exceptions. Dans la méthode startWork, vous devez renvoyer un 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 si votre tâche est arrêtée ? Le ListenableFuture d'une ListenableWorker est toujours annulé lorsque la tâche doit s'arrêter. À l'aide d'un 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