Como criar threads no ListenableWorker

Em determinadas situações, talvez seja necessário oferecer uma estratégia personalizada para linhas de execução. Por exemplo, talvez seja necessário processar uma operação assíncrona baseada em callback. O WorkManager oferece suporte a esse caso de uso com ListenableWorker. ListenableWorker é a API de worker mais básica. Worker, CoroutineWorker e RxWorker derivam dessa classe. Uma ListenableWorker apenas sinaliza quando o trabalho precisa iniciar e parar, deixando toda a linha de execução por sua conta. O sinal de início do trabalho é invocado na linha de execução principal. Por isso, é muito importante que você acesse manualmente uma linha de execução em segundo plano de sua escolha.

O método abstrato ListenableWorker.startWork() retorna um ListenableFuture do Result. Um ListenableFuture é uma interface leve: é um Future que fornece funcionalidade para anexar listeners e propagar exceções. No método startWork, espera-se que você retorne um ListenableFuture, que será definido com o Result da operação quando ela for concluída. É possível criar instâncias de ListenableFuture de duas maneiras:

  1. Se você usa Guava, use ListeningExecutorService.
  2. Caso contrário, inclua councurrent-futures no arquivo do Gradle e use CallbackToFutureAdapter.

Se você quisesse executar algum trabalho com base em um callback assíncrono, faria algo assim:

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

O que acontece se seu trabalho for interrompido? O ListenableFuture de um ListenableWorker é sempre cancelado quando se espera que o trabalho seja interrompido. Usando um CallbackToFutureAdapter, basta adicionar um listener de cancelamento da seguinte maneira:

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

Como executar um ListenableWorker em um processo diferente

Também é possível vincular um worker a um processo específico usando RemoteListenableWorker, uma implementação de ListenableWorker.

RemoteListenableWorker se vincula a um processo específico com dois argumentos extras que você fornece como parte dos dados de entrada ao criar a solicitação de trabalho: ARGUMENT_CLASS_NAME e ARGUMENT_PACKAGE_NAME.

O exemplo a seguir demonstra a criação de uma solicitação de trabalho vinculada a um processo específico:

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

Para cada RemoteWorkerService, também é preciso adicionar uma definição de serviço no arquivo 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>

Exemplos