Threads in ListenableWorker

In bestimmten Situationen müssen Sie möglicherweise eine benutzerdefinierte Threading-Strategie festlegen. Beispielsweise kann es sein, dass Sie einen Callback-basierten asynchronen Vorgang verarbeiten müssen. WorkManager unterstützt diesen Anwendungsfall mit ListenableWorker. ListenableWorker ist die einfachste Worker-API. Worker, CoroutineWorker und RxWorker leiten alle aus dieser Klasse ab. Ein ListenableWorker signalisiert nur, wann die Arbeit beginnen und beendet werden soll, und überlässt das Threading ganz dir. Das Startsignal wird im Hauptthread aufgerufen. Daher ist es sehr wichtig, dass Sie manuell einen Hintergrundthread Ihrer Wahl aufrufen.

Die abstrakte Methode ListenableWorker.startWork() gibt einen ListenableFuture von Result zurück. Eine ListenableFuture ist eine einfache Schnittstelle: Sie ist eine Future, die Funktionen zum Anhängen von Listenern und zur Weitergabe von Ausnahmen bietet. In der Methode startWork wird ein ListenableFuture zurückgegeben, das Sie nach Abschluss des Vorgangs mit dem Result des Vorgangs festlegen. Sie können ListenableFuture-Instanzen auf zwei Arten erstellen:

  1. Wenn du Guava verwendest, verwende ListeningExecutorService.
  2. Fügen Sie andernfalls councurrent-futures in die Gradle-Datei ein und verwenden Sie CallbackToFutureAdapter.

Wenn Sie Vorgänge basierend auf einem asynchronen Callback ausführen möchten, gehen Sie so vor:

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;
        });
    }
}

Was passiert, wenn Ihre Arbeit angehalten wird? ListenableFuture von ListenableWorker wird immer abgebrochen, wenn die Arbeit voraussichtlich beendet wird. Mit einem CallbackToFutureAdapter müssen Sie lediglich einen Abbruch-Listener hinzufügen:

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;
        });
    }
}

ListenableWorker in einem anderen Prozess ausführen

Sie können einen Worker auch mit RemoteListenableWorker, einer Implementierung von ListenableWorker, an einen bestimmten Prozess binden.

RemoteListenableWorker bindet sich mit zwei zusätzlichen Argumenten an einen bestimmten Prozess, die Sie beim Erstellen der Arbeitsanfrage als Teil der Eingabedaten angeben: ARGUMENT_CLASS_NAME und ARGUMENT_PACKAGE_NAME.

Das folgende Beispiel zeigt, wie eine Arbeitsanfrage erstellt wird, die an einen bestimmten Prozess gebunden ist:

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();

Für jede RemoteWorkerService müssen Sie außerdem eine Dienstdefinition in die AndroidManifest.xml-Datei einfügen:

<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>

Produktproben