Asynchroner Worker in Android WorkManager

Lesezeit: 7 Minuten

Benutzer-Avatar
Anton Tananajew

Google hat kürzlich neue angekündigt WorkManager Architekturkomponente. Es macht es einfach, synchrone Arbeit durch die Implementierung zu planen doWork() in Worker Klasse, aber was ist, wenn ich im Hintergrund asynchron arbeiten möchte? Ich möchte beispielsweise mit Retrofit einen Netzwerkdienstaufruf tätigen. Ich weiß, dass ich eine synchrone Netzwerkanfrage stellen kann, aber es würde den Thread blockieren und fühlt sich einfach falsch an. Gibt es dafür eine Lösung oder wird es im Moment einfach nicht unterstützt?

  • Du meinst, MainThread ist blockiert oder der aktuelle Thread ist blockiert?

    – Sagar

    18. Mai 2018 um 2:36 Uhr

  • Worker-Thread wird blockiert.

    – Anton Tananajew

    18. Mai 2018 um 2:37 Uhr


  • Anstatt einen neuen Thread zu erzeugen, können Sie zwei Worker gleichzeitig in die Warteschlange stellen?

    – Sagar

    18. Mai 2018 um 2:41 Uhr

  • Bitte lesen Sie die Frage sorgfältig durch. Ich eröffne keine neuen Threads.

    – Anton Tananajew

    18. Mai 2018 um 2:42 Uhr

  • Was ich meinte war, wenn Sie etwas asynchrones tun möchten, müssen Sie einen Thread erzeugen, damit er nicht auf demselben Thread ausgeführt wird. Ich versuche, Ihren Anwendungsfall zu verstehen.

    – Sagar

    18. Mai 2018 um 2:43 Uhr

Benutzer-Avatar
TomH

Ich habe einen Countdownlatch verwendet und darauf gewartet, dass dieser 0 erreicht, was erst geschieht, 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];

    }

  • Was passiert, wenn Constraint fehlschlägt. Bedeutet Einschränkung Für den Idealzustand löst der Arbeitsmanager aus. und nach einiger Zeit Telefon aus dem Idealzustand.

    – Nitisch

    24. November 2018 um 11:34 Uhr

Benutzer-Avatar
Bartholomäus Furche

FYI gibt es jetzt ListenableWorkerdie asynchron ausgelegt ist.

Bearbeiten: Hier sind einige Ausschnitte der Beispielverwendung. Ich habe große Teile des Codes herausgeschnitten, die meiner Meinung nach nicht illustrativ sind, daher besteht eine gute Chance, dass hier ein oder zwei kleinere Fehler enthalten sind.

Dies ist für eine Aufgabe, die einen String photoKey nimmt, Metadaten von einem Server abruft, einige Komprimierungsarbeiten durchführt und dann das komprimierte Foto hochlädt. Dies geschieht außerhalb des Hauptthreads. 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 im 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;
    }
}

BEARBEITEN

Abhängig von den Dingen, die Sie in Ihrer Anwendung verwenden, können Sie auch erweitern RxWorker (wenn Sie RxJava verwenden) oder CoroutineWorker (wenn Sie Coroutinen verwenden). Sie erstrecken sich beide von ListenableWorker.

  • Können Sie bitte ein Beispiel zur Verwendung dieser Klasse hinzufügen?

    – idisch

    27. November 2018 um 11:15 Uhr

  • @idish Ich habe ein Beispiel hinzugefügt.

    – Bartholomäusfurche

    4. Dezember 2018 um 20:54 Uhr

  • Ich kann SettableFuture.create() in Alpha-13 nicht verwenden, die Klasse ist nur auf dieselbe Bibliotheksgruppe beschränkt.

    – David Vávra

    15. Dezember 2018 um 12:40 Uhr

  • In der Tat SettableFuture.create(); -Modul ist nur für die WorkManager-Bibliotheksgruppe privat. Kann nicht benutzt werden.

    – idisch

    27. Dezember 2018 um 10:36 Uhr

  • Die Aufgabe wird im Hauptthread ausgeführt developer.android.com/reference/androidx/work/ListenableWorker. Sie sagen, dass The startWork() method is called on the main thread. Auch kann ich keine sehen onStartWork in der Klasse. Können Sie das erklären?

    – Abhay Pai

    3. Februar 2019 um 17:05 Uhr

Pro WorkManager-Dokumentation:

Standardmäßig führt WorkManager seine Operationen in einem Hintergrund-Thread aus. Wenn Sie bereits einen Hintergrund-Thread ausführen und synchrone (blockierende) Aufrufe an WorkManager benötigen, verwenden Sie synchron(), um auf solche Methoden zuzugreifen.

Daher, wenn Sie nicht verwenden synchronous()können Sie problemlos Synchronisierungsnetzwerkanrufe durchführen doWork(). Dies ist auch aus Designsicht ein besserer Ansatz, da Rückrufe chaotisch sind.

Das heißt, wenn Sie wirklich asynchrone Jobs abfeuern möchten doWork()müssen Sie den Ausführungsthread anhalten und nach Abschluss des asynchronen Auftrags mit verwenden wait/notify Mechanismus (oder ein anderer Thread-Verwaltungsmechanismus, z Semaphore). Nichts, was ich in den meisten Fällen empfehlen würde.

Nebenbei bemerkt, WorkManager befindet sich in einer sehr frühen Alpha-Phase.

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

Es gibt eine Reihe von Operatoren wie .blockingGet() oder .blockingFirst()
der sich verwandelt Observable<T> ins Sperren T

Worker führt auf Hintergrundthread aus, also mach dir keine Sorgen NetworkOnMainThreadException.

Benutzer-Avatar
Roman Nasarewitsch

Ich habe benutzt BlockingQueuedas die Synchronisierung von Threads und die Weitergabe von Ergebnissen zwischen Threads vereinfacht, benötigen Sie nur ein Objekt

private var disposable = Disposables.disposed()

private val completable = Completable.fromAction { 
        //do some heavy computation
    }.subscribeOn(Schedulers.computation()) // you will do the work on background thread

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

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

    return try {
        result.take() //need to block this thread untill completable has finished
    } catch (e: InterruptedException) {
        Result.RETRY
    }
}

Vergessen Sie auch nicht, Ressourcen freizugeben, wenn Ihr Worker gestoppt wurde, dies ist der Hauptvorteil gegenüber .blockingGet() wie jetzt können Sie Ihre Rx-Aufgabe ordnungsgemäß kostenlos stornieren.

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

  • Können Sie bitte mehr Code für denselben hinzufügen? Das ist ziemlich abstrakt

    – Vinayak

    11. August 2020 um 6:35 Uhr

Mit der Leistungsfähigkeit von Coroutinen können Sie die ‘synchronisieren’ doWork() so was:

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

Beispiel aufrufen in doWork():

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

Update 2021: Sie können jetzt verwenden CoroutineWorkerdie ausgesetzt hat doWork() Methode.

class MySuspendWorker(private val appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {
    override suspend fun doWork(): Result {
        //do your async work
    }
}

  • Können Sie bitte mehr Code für denselben hinzufügen? Das ist ziemlich abstrakt

    – Vinayak

    11. August 2020 um 6:35 Uhr

Benutzer-Avatar
Mostafa Gazar

Ich würde auch den von @TomH empfohlenen Ansatz bevorzugen. Ich habe es jedoch mit Firebase Storage verwendet. Verwenden von WorkManager zusammen mit CountDownlatch hat es mir angetan. Hier ein Codeschnipsel. Protokolle sind fertig Holz.

Es gibt die downloadUrl von Firebase als String zurück, nachdem die Aufgabe abgeschlossen ist, aber bevor der Worker Erfolg zurückgibt.

@NonNull
@Override
public Result doWork() {
    mFirebaseStorage = mFirebaseStorage.getInstance();
    mTriviaImageStorageReference = mFirebaseStorage.getReference().child("images");

    CountDownLatch countDown = new CountDownLatch(2);
    Uri imageUri = Uri.parse(getInputData().getString(KEY_IMAGE_URI));

    try {

    // get the image reference
    final StorageReference imageRef = mTriviaImageStorageReference.child(imageUri.getLastPathSegment());

    // upload the image to Firebase
    imageRef.putFile(imageUri).continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
        @Override
        public Task<Uri> then(@NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
            if (!task.isSuccessful()) {
                throw task.getException();
            }
            countDown.countDown();
            return imageRef.getDownloadUrl();
        }
    }).addOnCompleteListener(new OnCompleteListener<Uri>() {
        @Override
        public void onComplete(@NonNull Task<Uri> task) {
            if (task.isSuccessful()) {
                Timber.d("Image was successfully uploaded to Firebase");
                Uri downloadUri = task.getResult();
                String imageUrl = downloadUri.toString();

                Timber.d(("URl of the image is: " + imageUrl));

                mOutputData = new Data.Builder()
                        .putString(KEY_FIREBASE_IMAGE_URL, imageUrl)
                        .build();
                countDown.countDown();
            } else {
                Toast.makeText(getApplicationContext(), "upload failed", Toast.LENGTH_SHORT).show();
                countDown.countDown();
            }
        }
    });
    countDown.await();
    return Result.success(mOutputData);

    } catch (Throwable throwable) {
        Timber.e(throwable, "Error uploading image");
        return Result.failure();
    }
}

  • Ich habe es auf deine Art versucht, aber mein doWork wird mehrmals aufgerufen,

    – gr_aman

    24. Mai 2020 um 12:32 Uhr

1136590cookie-checkAsynchroner Worker in Android WorkManager

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy