web-dev-qa-db-ja.com

Android WorkManagerの非同期ワーカー

Googleは最近、新しいWorkManagerアーキテクチャコンポーネントを発表しました。 doWork()Workerクラスに実装することにより、同期作業を簡単にスケジュールできますが、バックグラウンドで非同期作業を行いたい場合はどうすればよいですか?たとえば、Retrofitを使用してネットワークサービス呼び出しを行いたいです。同期ネットワーク要求を作成できることは知っていますが、スレッドをブロックし、間違っていると感じるだけです。これに対する解決策はありますか、それとも現時点ではサポートされていませんか?

24
Anton Tananaev

WorkManagerドキュメント

デフォルトでは、WorkManagerはバックグラウンドスレッドで操作を実行します。すでにバックグラウンドスレッドで実行しており、WorkManagerの同期(ブロック)呼び出しが必要な場合は、synchronous()を使用してこのようなメソッドにアクセスします。

したがって、synchronous()を使用しない場合、doWork()から同期ネットワーク呼び出しを安全に実行できます。コールバックは面倒なので、これは設計の観点からもより良いアプローチです。

ただし、doWork()から非同期ジョブを本当に起動したい場合は、wait/notifyメカニズム(またはSemaphoreなどの他のスレッド管理メカニズムを使用して、非同期ジョブの完了時に実行スレッドを一時停止して再開する必要があります)。ほとんどの場合、私が推奨するものではありません。

補足として、WorkManagerは初期のアルファ版です。

11
Vasiliy

Countdownlatchを使用して、これが0に達するのを待ちました。これは、非同期コールバックが更新した後にのみ発生します。このコードを参照してください:

public WorkerResult doWork() {

        final WorkerResult[] result = {WorkerResult.RETRY};
        CountDownLatch countDownLatch = new CountDownLatch(1);
        FirebaseFirestore db = FirebaseFirestore.getInstance();

        db.collection("collection").whereEqualTo("this","that").get().addOnCompleteListener(task -> {
            if(task.isSuccessful()) {
                task.getResult().getDocuments().get(0).getReference().update("field", "value")
                        .addOnCompleteListener(task2 -> {
                            if (task2.isSuccessful()) {
                                result[0] = WorkerResult.SUCCESS;
                            } else {
                                result[0] = WorkerResult.RETRY;
                            }
                            countDownLatch.countDown();
                        });
            } else {
                result[0] = WorkerResult.RETRY;
                countDownLatch.countDown();
            }
        });

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return result[0];

    }
21
user8159708

参考までに ListenableWorker があります。これは非同期に設計されています。

編集:使用例の一部を以下に示します。例示的ではないと思われる大きなコードの塊を切り取ったので、ここに小さなエラーが1つまたは2つある可能性があります。

これは、String photoKeyを取得し、サーバーからメタデータを取得し、圧縮作業を実行してから、圧縮された写真をアップロードするタスク用です。これはメインスレッドで発生します。作業リクエストを送信する方法は次のとおりです。

private void compressAndUploadFile(final String photoKey) {
    Data inputData = new Data.Builder()
            .putString(UploadWorker.ARG_PHOTO_KEY, photoKey)
            .build();
    Constraints constraints = new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build();
    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(UploadWorker.class)
            .setInputData(inputData)
            .setConstraints(constraints)
            .build();
    WorkManager.getInstance().enqueue(request);
}

そしてUploadWorkerで:

public class UploadWorker extends ListenableWorker {
    private static final String TAG = "UploadWorker";
    public static final String ARG_PHOTO_KEY = "photo-key";

    private String mPhotoKey;

    /**
     * @param appContext   The application {@link Context}
     * @param workerParams Parameters to setup the internal state of this worker
     */
    public UploadWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
        mPhotoKey = workerParams.getInputData().getString(ARG_PHOTO_KEY);
    }

    @NonNull
    @Override
    public ListenableFuture<Payload> onStartWork() {
        SettableFuture<Payload> future = SettableFuture.create();
        Photo photo = getPhotoMetadataFromServer(mPhotoKey).addOnCompleteListener(task -> {
            if (!task.isSuccessful()) {
                Log.e(TAG, "Failed to retrieve photo metadata", task.getException());
                future.setException(task.getException());
                return;
            }
            MyPhotoType photo = task.getResult();
            File file = photo.getFile();
            Log.d(TAG, "Compressing " + photo);
            MyImageUtil.compressImage(file, MyConstants.photoUploadConfig).addOnCompleteListener(compressionTask -> {
                if (!compressionTask.isSuccessful()) {
                    Log.e(TAG, "Could not parse " + photo + " as an image.", compressionTask.getException());
                    future.set(new Payload(Result.FAILURE));
                    return;
                }
                byte[] imageData = compressionTask.getResult();
                Log.d(TAG, "Done compressing " + photo);
                UploadUtil.uploadToServer(photo, imageData);
                future.set(new Payload(Result.SUCCESS));
            });
        });
        return future;
    }
}
17

非同期ジョブについて話している場合は、RxJava Observables/Singlesに作業を移動できます。

Observable<T>をブロッキングTに変換する.blockingGet().blockingFirst()などの演算子のセットがあります

Workerはバックグラウンドスレッドで実行されるため、NetworkOnMainThreadExceptionについて心配する必要はありません。

7
Lukas

BlockingQueueを使用しました。これにより、スレッドの同期と結果の受け渡しが簡単になり、必要なオブジェクトは1つだけになります。

private var disposable = Disposables.disposed()


override fun doWork(): Result {
    val result = LinkedBlockingQueue<Result>()

    disposable = completable.subscribe(
            { result.put(Result.SUCCESS) },
            { result.put(Result.RETRY) }
    )

    return try {
        result.take()
    } catch (e: InterruptedException) {
        Result.RETRY
    }
}

また、ワーカーが停止している場合は、リソースを解放することを忘れないでください。これは、.blockingGet()に対する主な利点です。Rxタスクを適切にキャンセルできるようになりました。

override fun onStopped(cancelled: Boolean) {
    disposable.dispose()
}
1

コルーチンの力により、次のようにdoWork()を「同期」できます。

場所を取得するための一時停止メソッド(非同期):

private suspend fun getLocation(): Location = suspendCoroutine { continuation ->
    val mFusedLocationClient = LocationServices.getFusedLocationProviderClient(appContext)
    mFusedLocationClient.lastLocation.addOnSuccessListener {
        continuation.resume(it)
    }.addOnFailureListener {
        continuation.resumeWithException(it)
    }
}

doWork()の例を呼び出す:

override fun doWork(): Result {
    val loc = runBlocking {
        getLocation()
    }
    val latitude = loc.latitude
}
0
Webfreak