Как сериализовать асинхронные вызовы с помощью синхронизированного блока

#java #android #firebase #firebase-storage

# #java #Android #firebase #firebase-хранилище

Вопрос:

Я использую Firebase для своего бэкэнда, а на стороне клиента (Android) я пытаюсь загрузить кучу изображений по порядку. Я заключил итератор в synchronized блок и жду загрузки каждого изображения.

 private Object mLock = new Object();

private void downloadImages() {
   List<StorageReference> storageReferences = getStorageReferences();

   synchronized (mLock) {
      // Iterate trough all image references 
      for (StorageReference sr : storageReferences) {
         sr.getBytes(ONE_MB_BUFFER).addOnCompleteListener(new OnCompleteListener<byte[]>() {
            @Override
            public void onComplete(Task<byte[]> task) {
               if (task.isSuccessful()) {
                  // Success, image downloaded
               }

               // Notify, that we have downloaded the image
               synchronized (mLock) {
                  mLock.notify();
               }
            }
         });

         // Await until we acquire the lock
         try {
            mLock.wait();
         }
         catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
}
 

Обратный вызов в addOnCompleteListener does не вызывается. На самом деле, весь поток заблокирован.

Есть ли какая-либо другая альтернатива постановке задач загрузки в очередь? Как однопоточная служба исполнителя?

Ответ №1:

В итоге я использовал ExecutorService with newSingleThreadExecutor . Если вам нужны другие настройки, например, тайм-аут, вы можете использовать newScheduledThreadPool . Вы можете создать пул потоков и выполнять несколько потоков одновременно.

 public class ImageDownloadService extends IntentService {

   @Override
   protected void onHandleIntent(Intent intent) {
      downloadImages();
   }  

   private void downloadImages() {
      ExecutorService executor = Executors.newSingleThreadExecutor();

      List<StorageReference> storageReferences = getStorageReferences();

      for (StorageReference sr : storageReferences) {
         Future<byte[]> future = executor.submit(new FutureImageResult(sr));

         byte[] data = null;

         try {
            data = future.get();
         } catch (InterruptedException e) {
            e.printStackTrace();
         } catch (ExecutionException e) {
            e.printStackTrace();
         }

         if (data != null amp;amp; data.length > 0) {
            // Image downloaded successfully
         }
      }
   }   

}
 

future Который отправляется службе исполнителя.

 public class FutureImageResult implements Callable<byte[]> {

    private StorageReference mStorageReference;

    private boolean mIsFailure;

    public FutureImageResult(StorageReference storageReference) {
        mStorageReference = storageReference;
    }

    @Override
    public byte[] call() throws Exception {
        Task<byte[]> task = mStorageReference.getBytes(1024 * 1024);
        task.addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                mIsFailure = true;
            }
        });

        while (!task.isComplete() || mIsFailure);
        byte[] data = task.getResult();

        return data;
    }
}
 

Ответ №2:

Другой подход заключается в использовании BlockingQueue .

Общая идея такова:

  • вы запускаете поток, который непрерывно опрашивает из очереди, загружает заданное изображение и начинает все сначала
  • вы помещаете все URL-адреса в очередь
  • прослушиватель связан либо с потоком, либо с каждым изображением

Более менее требуемый код:

 final BlockingQueue<URL> queue = new LinkedBlockingQueue<>();
new Thread(new Runnable() {
    @Override
    public void run() {
        while (true) {
            URL url = queue.poll();
            // Download the image and notify the listener               
        }
    }
}).start();
 

Этот поток может быть запущен службой, чтобы он мог продолжать работать и не привязан к пользовательскому интерфейсу. Затем действия могут привязать Службу к взаимодействию с ней.

Ответ №3:

Вы также можете использовать CountDownLatch блокировку рабочего потока до завершения операции. Что-то вроде этого:

 private void downloadImages() {
   List<StorageReference> storageReferences = getStorageReferences();
   CountDownLatch waitForDownload = new CountDownLatch(storageReferences.size());
   // Iterate trough all image references 
   for (StorageReference sr : storageReferences) {
         sr.getBytes(ONE_MB_BUFFER).addOnCompleteListener(new OnCompleteListener<byte[]>() {
            @Override
            public void onComplete(Task<byte[]> task) {
               // Notify, that we have downloaded the image and continue
               waitForDownload.countDown();
            }
         });
   }
   // Lock until we download all images
   waitForDownload.await();
   // Continue with the rest of your serialized work having all images downloaded
   ...
}
 

Ссылки: CountDownLatch javadoc.