it-swarm.com.de

Asynchroner Arbeiter in Android WorkManager

Google hat kürzlich die neue WorkManager-Architekturkomponente angekündigt. Durch die Implementierung von doWork() in der Klasse Worker ist es einfach, synchrone Arbeit zu planen. Was aber, wenn ich etwas asynchrone Arbeit im Hintergrund ausführen möchte? Ich möchte beispielsweise einen Netzdienstanruf mit Retrofit durchführen. Ich weiß, ich kann eine synchrone Netzwerkanfrage machen, aber dies würde den Thread blockieren und fühlt sich einfach falsch an. Gibt es eine Lösung oder wird sie gerade nicht unterstützt?

16
Anton Tananaev

Pro WorkManager-Dokumente :

Standardmäßig führt WorkManager seine Operationen in einem Hintergrundthread aus. Wenn Sie bereits in einem Hintergrundthread ausgeführt werden und synchrone (blockierende) Aufrufe von WorkManager benötigen, verwenden Sie synchronous (), um auf solche Methoden zuzugreifen. 

Wenn Sie synchronous() nicht verwenden, können Sie also sicher Netzwerkaufrufe von doWork() aus durchführen. Dies ist auch aus Designsicht ein besserer Ansatz, da Rückrufe unordentlich sind.

Wenn Sie wirklich asynchrone Jobs von doWork() auslösen möchten, müssen Sie den Ausführungsthread anhalten und nach Abschluss des asynchronen Jobs mit wait/notify (oder einem anderen Thread-Verwaltungsmechanismus, beispielsweise Semaphore) fortsetzen. In den meisten Fällen würde ich nichts empfehlen.

Als Nebenbemerkung befindet sich WorkManager in einem sehr frühen Alpha.

9
Vasiliy

Ich habe einen Countdownlatch verwendet und darauf gewartet, dass 0 erreicht wird. Dies wird erst dann geschehen, wenn der asynchrone Callback ihn aktualisiert hat. Siehe diesen Code:

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

    }
20
user8159708

Zu Ihrer Information gibt es jetzt ListenableWorker , die asynchron ausgelegt ist.

Bearbeiten: Hier sind einige Ausschnitte der Beispielnutzung. Ich habe große Codeabschnitte herausgeschnitten, von denen ich denke, dass sie nicht zur Veranschaulichung dienen. Es besteht also eine gute Chance, dass hier ein paar Fehler auftreten.

Dies ist für eine Aufgabe, die einen String photoKey nimmt, Metadaten von einem Server abruft, einige Komprimierungsarbeiten ausführt und dann das komprimierte Foto hochlädt. Dies geschieht aus dem Haupt-Thread. So senden wir die Arbeitsanfrage:

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

Und in 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;
    }
}
10

Wenn Sie über asynchronen Job sprechen, können Sie Ihre Arbeit in RxJava Observables/Singles verschieben. 

Es gibt eine Reihe von Operatoren wie .blockingGet() oder .blockingFirst(), Die Observable<T> in blockierende T verwandelt. 

Worker führt einen Hintergrundthread durch, machen Sie sich also keine Sorgen über NetworkOnMainThreadException.

6
Lukas

Ich habe BlockingQueue verwendet, das das Synchronisieren von Threads und das Übergeben von Ergebnissen vereinfacht. Sie benötigen nur ein Objekt

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

Vergessen Sie auch nicht, Ressourcen freizugeben, wenn Ihr Worker gestoppt wurde. Dies ist der Hauptvorteil gegenüber .blockingGet(), da Sie jetzt die Rx-Aufgabe ordnungsgemäß abbrechen können.

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

Mit der Kraft von Coroutinen können Sie die Funktion doWork() folgendermaßen 'synchronisieren':

Suspend-Methode zum Abrufen des Standorts (asynchron):

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

Aufrufbeispiel in doWork():

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