Threading di ListenableWorker

Dalam situasi tertentu, Anda mungkin perlu menyediakan strategi threading kustom. Misalnya, Anda mungkin perlu menangani operasi asinkron berbasis callback. WorkManager mendukung kasus penggunaan ini dengan ListenableWorker. ListenableWorker adalah API pekerja paling dasar; Worker, CoroutineWorker, dan RxWorker semuanya berasal dari class ini. ListenableWorker hanya memberikan sinyal kapan pekerjaan harus dimulai dan dihentikan, serta menyerahkan threading sepenuhnya kepada Anda. Sinyal pekerjaan awal dipanggil di thread utama, jadi sangat penting bagi Anda untuk membuka thread latar belakang pilihan Anda secara manual.

Metode abstrak ListenableWorker.startWork() menampilkan ListenableFuture dari Result. ListenableFuture adalah antarmuka ringan: ini adalah Future yang menyediakan fungsi untuk melampirkan pemroses dan menyebarkan pengecualian. Dalam metode startWork, Anda diharapkan menampilkan ListenableFuture, yang akan Anda tetapkan dengan Result operasi setelah selesai. Anda dapat membuat instance ListenableFuture dengan salah satu dari dua cara berikut:

  1. Jika Anda menggunakan Guava, gunakan ListeningExecutorService.
  2. Jika tidak, sertakan councurrent-futures dalam file gradle Anda dan gunakan CallbackToFutureAdapter.

Jika ingin menjalankan beberapa tugas berdasarkan callback asinkron, Anda harus melakukannya seperti ini:

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

Apa yang terjadi jika pekerjaan Anda dihentikan? ListenableFuture ListenableWorker selalu dibatalkan saat pekerjaan diharapkan akan berhenti. Dengan menggunakan CallbackToFutureAdapter, Anda hanya perlu menambahkan pemroses pembatalan, seperti berikut:

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

Menjalankan ListenableWorker dalam proses yang berbeda

Anda juga dapat mengikat pekerja ke proses tertentu menggunakan RemoteListenableWorker, implementasi ListenableWorker.

RemoteListenableWorker terikat ke proses tertentu dengan dua argumen tambahan yang Anda berikan sebagai bagian dari data input saat membuat permintaan pekerjaan: ARGUMENT_CLASS_NAME dan ARGUMENT_PACKAGE_NAME.

Contoh berikut menunjukkan pembuatan permintaan pekerjaan yang terikat dengan proses tertentu:

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

Untuk setiap RemoteWorkerService, Anda juga perlu menambahkan definisi layanan di file 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>

Contoh