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?
Asynchroner Worker in Android WorkManager
Anton Tananajew
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
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 sehenonStartWork
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
.
Roman Nasarewitsch
Ich habe benutzt BlockingQueue
das 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 CoroutineWorker
die 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
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
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