49

谷歌最近宣布了新的WorkManager架构组件。doWork()通过在课堂上实现可以很容易地安排同步工作Worker,但是如果我想在后台做一些异步工作怎么办?例如,我想使用 Retrofit 进行网络服务调用。我知道我可以发出同步网络请求,但它会阻塞线程并且感觉不对。是否有任何解决方案,或者目前不支持?

4

8 回答 8

33

我使用了一个 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];

    }
于 2018-05-19T12:15:26.933 回答
29

仅供参考,现在有ListenableWorker,它被设计为异步的。

编辑:以下是一些示例用法片段。我删掉了我认为不是说明性的大块代码,所以这里很有可能有一两个小错误。

这适用于接受 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;
    }
}

编辑

根据您在应用程序中使用的内容,您还可以扩展RxWorker(如果您使用 RxJava)或CoroutineWorker(如果您使用 Coroutines)。它们都从ListenableWorker扩展而来。

于 2018-10-15T22:27:59.763 回答
18

每个WorkManager 文档

默认情况下,WorkManager 在后台线程上运行其操作。如果您已经在后台线程上运行并且需要对 WorkManager 进行同步(阻塞)调用,请使用 synchronous() 来访问此类方法。

因此,如果您不使用synchronous(),您可以安全地从 执行同步网络调用doWork()。从设计的角度来看,这也是一种更好的方法,因为回调很混乱。

也就是说,如果您真的想从中触发异步作业,则doWork()需要暂停执行线程并使用wait/notify机制(或其他一些线程管理机制,例如Semaphore)在异步作业完成时恢复它。在大多数情况下,我不会推荐。

作为旁注,WorkManager 处于非常早期的 alpha 阶段。

于 2018-05-18T02:48:13.150 回答
7

如果你在谈论异步工作,你可以将你的工作转移到 RxJava Observables / Singles 中。

有一组类似.blockingGet()or的运算符.blockingFirst() 转换Observable<T>为阻塞T

Worker在后台线程上执行所以不用担心NetworkOnMainThreadException

于 2018-05-21T05:54:26.277 回答
6

我用过BlockingQueue,简化了线程同步和线程之间传递结果,你只需要一个对象

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

如果您的 Worker 已停止,请不要忘记释放资源,这是主要优势,.blockingGet()因为现在您可以正确地免费取消您的 Rx 任务。

override fun onStopped(cancelled: Boolean) {
    disposable.dispose()
}
于 2018-07-24T15:29:18.920 回答
3

借助协程的强大功能,您可以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
}

2021 年更新:您现在可以使用CoroutineWorker具有暂停doWork()方法的 。

class MySuspendWorker(private val appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {
    override suspend fun doWork(): Result {
        //do your async work
    }
}
于 2019-07-04T12:12:05.757 回答
0

我也更喜欢@TomH 推荐的方法。不过,我将它与 Firebase Storage 一起使用。将 WorkManager 与CountDownlatch一起使用对我来说是成功的。这是一个代码片段。日志是用Timber完成的。

在任务完成后但在工作人员返回成功之前,它会从 Firebase 作为字符串返回 downloadUrl。

@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();
    }
}
于 2020-05-15T19:33:10.867 回答
0

这已经晚了,但这可能会帮助其他人,

您可以CoroutineWorker在 doWork() 中使用 and ,使用名为 的东西suspendCancellableCoroutine,它是为此目的而明确设计的。

下面是代码片段:

class FileDownloader(private val appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {

   override suspend fun doWork(): Result {

       try {

          suspendCancellableCoroutine<Int> { cancellableContinuation ->

              // Here you can call your asynchronous callback based network

                override fun onComplete() {
                        cancellableContinuation.resumeWith(
                            kotlin.Result.success(100))
                }

                override fun onError(error: Error?) {

                        cancellableContinuation.resumeWithException(
                            error?.connectionException ?: Throwable()
                        )
                   
               }
               
     }

     }catch (e: Exception) {
           return Result.failure()
      }

  return Result.success()
}
}

在这里,协程将停止,直到您调用cancellableContinuation.resumeWith。

于 2020-10-01T08:16:41.810 回答