Загрузка файла PDF с помощью BlockingQueue

Я пытаюсь загрузить pdf файл с помощью URLConnection. Здесь, как я устанавливаю объект соединения.

URL serverUrl = new URL(url);
urlConnection = (HttpURLConnection) serverUrl.openConnection();
urlConnection.setDoInput(true);
urlConnection.setRequestMethod("GET");
urlConnection.setRequestProperty("Content-Type", "application/pdf");
urlConnection.setRequestProperty("ENCTYPE", "multipart/form-data");
String contentLength = urlConnection.getHeaderField("Content-Length");

Я получил входной поток от объекта соединения.

bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());

И выходной поток для записи содержимого файла.

File dir = new File(context.getFilesDir(), mFolder);
if(!dir.exists()) dir.mkdir();
final File f = new File(dir, String.valueOf(documentName));
f.createNewFile();
final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(f, true)); //true for appendMode

BlockingQueue создается таким образом, что потоки, выполняющие операции чтения и записи, могут обращаться к очереди.

final BlockingQueue<ByteArrayWrapper> blockingQueue = new ArrayBlockingQueue<ByteArrayWrapper>(MAX_VALUE,true);
final byte[] dataBuffer = new byte[MAX_VALUE];

Теперь создается поток для чтения данных из InputStream.

Thread readerThread = new Thread(new Runnable() {
       @Override
       public void run() {
         try {
            int count = 0;
            while((count = bufferedInputStream.read(dataBuffer, 0, dataBuffer.length)) != -1) {
                 ByteArrayWrapper byteArrayWrapper = new ByteArrayWrapper(dataBuffer);
                 byteArrayWrapper.setBytesReadCount(count);
                 blockingQueue.put(byteArrayWrapper);
             }
             blockingQueue.put(null); //end of file
          } catch(Exception e) {
                 e.printStackTrace();
          } finally {
              try {
                 bufferedInputStream.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
       }
 });

Теперь поток писателя считывает эти файлы.

Thread writerThread = new Thread(new Runnable() {
       @Override
       public void run() {
         try {
            while(true) {
               ByteArrayWrapper byteWrapper = blockingQueue.take();
               if(null == byteWrapper) break;
               bufferedOutputStream.write(byteWrapper.getBytesRead(), 0, byteWrapper.getBytesReadCount());
             }
             bufferedOutputStream.flush();
         } catch(Exception e) {
              e.printStackTrace();
         } finally {
              try {
                 bufferedOutputStream.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
         }
      }
});

Наконец, потоки запускаются.

readerThread.start();
writerThread.start();
Теоретически он должен прочитать файл из InputStream и сохранить его в целевом файле. Однако на самом деле он создает пустой файл PDF. В какой-то другой момент он показывает неверное исключение в формате pdf. Размер файла совпадает с длиной содержимого InputStream. Есть что-то, чего я не хватает?

Ответ 1

Я не знаком с ByteArrayWrapper. Он просто держит ссылку на массив, как это?

public class ByteArrayBuffer {
    final private byte[] data;

    public ByteArrayBuffer(byte[] data) {
        this.data = data;
    }

    public byte[] getBytesRead() {
        return data;
    }

    /*...etc...*/
} 

Если это так. это будет проблемой: все объекты ByteArrayWrapper поддерживаются одним и тем же массивом. Это неоднократно перезаписывается автором. Несмотря на то, что BlockingQueue сделал тяжелую работу по безопасному публикации каждого объекта из одного потока в другой.

Самое простое исправление может заключаться в том, чтобы сделать ByteArrayWrapper эффективно неизменным, т.е. не изменять его после публикации его в другой поток. Присвоение копии массива при построении было бы самым простым:

public ByteArrayWrapper(byte[] data) {
    this.data = Arrays.copyOf(data, data.length);
}

Другая проблема заключается в том, что "BlockingQueue не принимает нулевые элементы" (см. BlockingQueue docs), и поэтому "конец ввода" значение не работает. Заменяя null на

private static ByteArrayWrapper END = new ByteArrayWrapper(new byte[]{});

в соответствующих местах это исправит.

Сделав эти изменения в копии кода, я смог получить точную копию PDF файла.

Ответ 2

Попробуйте использовать Android DownloadManager (http://developer.android.com/reference/android/app/DownloadManager.html), он используется для обработки длинных HTTP-запросов в фоновом режиме. Здесь вам не нужно думать о полученных байтах, и прогресс отображается на панели уведомлений.

Здесь есть хороший учебник: http://blog.vogella.com/2011/06/14/android-downloadmanager-example/