Не удается получить прогресс при загрузке файла http POST (Android) - PullRequest
17 голосов
/ 09 июля 2010

Я занимаюсь разработкой приложения для Android, которое позволяет пользователю загружать файлы в такие службы, как Twitpic и другие. Загрузка POST выполняется без каких-либо внешних библиотек и работает просто отлично. Моя единственная проблема в том, что я не могу получить никакого прогресса, потому что вся загрузка выполняется, когда я получаю ответ, а не , записывающий байты в выходной поток. Вот что я делаю:

URL url = new URL(urlString); 
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 
conn.setDoInput(true); 
conn.setDoOutput(true); 
conn.setUseCaches(false); 
conn.setRequestMethod("POST"); 
conn.setRequestProperty("Connection", "Keep-Alive"); 
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); 
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());

Затем я записываю данные формы в dos, что сейчас не так важно. После этого я записываю данные самого файла (считывая из «in», который является InputStream данных, которые я хочу отправить):

while ((bytesAvailable = in.available()) > 0)
{ 
 bufferSize = Math.min(bytesAvailable, maxBufferSize);
 byte[] buffer = new byte[bufferSize];
 bytesRead = in.read(buffer, 0, bufferSize);
 dos.write(buffer, 0, bytesRead);
} 

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

in.close(); 
dos.flush(); 
dos.close(); 

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

DataInputStream inStream = new DataInputStream(conn.getInputStream());

Это занимает несколько секунд или минут, в зависимости от размера файла и скорости интернет-соединения. Мои вопросы сейчас: 1) Почему не происходит uplaod, когда я записываю байты в "dos"? 2) Как я могу получить прогресс, чтобы показать диалог прогресса во время загрузки, когда все это происходит одновременно?

/ EDIT: 1) Я устанавливаю Content-Length в заголовке, который немного меняет проблему, но не в любом способ решить это: Теперь весь контент загружается после записи последнего байта в поток. Таким образом, это не меняет ситуацию, когда вы не можете отследить прогресс, потому что снова данные записываются сразу. 2) Я попробовал MultipartEntity в Apache HttpClient v4. Там у вас вообще нет OutputStream, потому что все данные записываются при выполнении запроса. Опять же, нет никакого способа добиться прогресса.

Есть ли кто-нибудь, кто имеет какую-либо другую идею, как получить процесс при загрузке из нескольких частей / форм?

Ответы [ 4 ]

20 голосов
/ 14 июля 2010

Я очень много пытался в последние дни, и я думаю, что у меня есть ответ на первоначальный вопрос:

Невозможно получить прогресс, используя HttpURLConnection, потому что в Android есть ошибка / необычное поведение: http://code.google.com/p/android/issues/detail?id=3164#c6

Это будет исправлено после Froyo, чего нельзя ждать ...

Итак, единственный другой вариант, который я нашел, - это использовать Apache HttpClient.Ответ , связанный с papleu , верен, но относится к общему Java.Используемые там классы больше не доступны в Android (когда-то HttpClient 3.1 был частью SDK).Итак, что вы можете сделать, это добавить библиотеки из HttpClient 4 (в частности, apache-mime4j-0.6.jar и httpmime-4.0.1.jar) и использовать комбинацию первого ответа (от Tuler) и последнего ответа (от Hamy).):

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;

public class CountingMultipartEntity extends MultipartEntity {

    private final ProgressListener listener;

    public CountingMultipartEntity(final ProgressListener listener) {
        super();
        this.listener = listener;
    }

    public CountingMultipartEntity(final HttpMultipartMode mode, final ProgressListener listener) {
        super(mode);
        this.listener = listener;
    }

    public CountingMultipartEntity(HttpMultipartMode mode, final String boundary,
            final Charset charset, final ProgressListener listener) {
        super(mode, boundary, charset);
        this.listener = listener;
    }

    @Override
    public void writeTo(final OutputStream outstream) throws IOException {
        super.writeTo(new CountingOutputStream(outstream, this.listener));
    }

    public static interface ProgressListener {
        void transferred(long num);
    }

    public static class CountingOutputStream extends FilterOutputStream {

        private final ProgressListener listener;
        private long transferred;

        public CountingOutputStream(final OutputStream out,
                final ProgressListener listener) {
            super(out);
            this.listener = listener;
            this.transferred = 0;
        }


        public void write(byte[] b, int off, int len) throws IOException {
            out.write(b, off, len);
            this.transferred += len;
            this.listener.transferred(this.transferred);
        }

        public void write(int b) throws IOException {
            out.write(b);
            this.transferred++;
            this.listener.transferred(this.transferred);
        }
    }
}

Это работает с HttpClient 4, но недостатком является то, что мой apk теперь имеет размер 235 КБ (это было 90 КБ, когда я использовал составную загрузку, описанную в моем вопросе), и дажехуже того, размер установленного приложения составляет 735 КБ (около 170 КБ раньше).Это действительно ужасно.Только для того, чтобы добиться прогресса во время загрузки, размер приложения теперь более чем в 4 раза больше, чем был раньше.

1 голос
/ 01 мая 2014

Попробуйте это. Это работает.

connection = (HttpURLConnection) url_stripped.openConnection();
    connection.setRequestMethod("PUT");
    String boundary = "---------------------------boundary";
    String tail = "\r\n--" + boundary + "--\r\n";
    connection.addRequestProperty("Content-Type", "image/jpeg");
    connection.setRequestProperty("Connection", "Keep-Alive");
    connection.setRequestProperty("Content-Length", ""
            + file.length());
    connection.setDoOutput(true);

    String metadataPart = "--"
            + boundary
            + "\r\n"
            + "Content-Disposition: form-data; name=\"metadata\"\r\n\r\n"
            + "" + "\r\n";

    String fileHeader1 = "--"
            + boundary
            + "\r\n"
            + "Content-Disposition: form-data; name=\"uploadfile\"; filename=\""
            + fileName + "\"\r\n"
            + "Content-Type: application/octet-stream\r\n"
            + "Content-Transfer-Encoding: binary\r\n";

    long fileLength = file.length() + tail.length();
    String fileHeader2 = "Content-length: " + fileLength + "\r\n";
    String fileHeader = fileHeader1 + fileHeader2 + "\r\n";
    String stringData = metadataPart + fileHeader;

    long requestLength = stringData.length() + fileLength;
    connection.setRequestProperty("Content-length", ""
            + requestLength);
    connection.setFixedLengthStreamingMode((int) requestLength);
    connection.connect();

    DataOutputStream out = new DataOutputStream(
            connection.getOutputStream());
    out.writeBytes(stringData);
    out.flush();

    int progress = 0;
    int bytesRead = 0;
    byte buf[] = new byte[1024];
    BufferedInputStream bufInput = new BufferedInputStream(
            new FileInputStream(file));
    while ((bytesRead = bufInput.read(buf)) != -1) {
        // write output
        out.write(buf, 0, bytesRead);
        out.flush();
        progress += bytesRead;
        // update progress bar
        publishProgress(progress);
    }

    // Write closing boundary and close stream
    out.writeBytes(tail);
    out.flush();
    out.close();

    // Get server response
    BufferedReader reader = new BufferedReader(
            new InputStreamReader(connection.getInputStream()));
    String line = "";
    StringBuilder builder = new StringBuilder();
    while ((line = reader.readLine()) != null) {
        builder.append(line);
    }

Ссылка: http://delimitry.blogspot.in/2011/08/android-upload-progress.html

0 голосов
/ 28 марта 2012

Вместо этого используйте WatchedInputStream. Это красиво, здорово и просто в использовании.

  1. используйте WatchedInputStream для переноса вашего входного потока.
  2. watchedStream.setListener

    watchedStream.setListener (новый WatchedInputStream.Listener () {

    public void notify (int read) { // сделать что-нибудь для обновления пользовательского интерфейса.
    } }

0 голосов
/ 05 марта 2012

Можно получить прогресс из DefaultHttpClient. Тот факт, что он остается в линии

response = defaultHttpClient.execute( httpput, context );

до тех пор, пока не будут отправлены полные данные, не обязательно означает, что вы не можете получить информацию о ходе выполнения. А именно, HttpContext изменяется при выполнении этой строки, поэтому, если вы обращаетесь к ней через другой поток, вы можете наблюдать изменения в этом. Что вам нужно, так это ConnAdapter. Он имеет встроенный HttpConnectionMetrics

final AbstractClientConnAdapter connAdapter = (AbstractClientConnAdapter) context.getAttribute(ExecutionContext.HTTP_CONNECTION);
final HttpConnectionMetrics metrics = connAdapter.getMetrics();
int bytesSent = (int)metrics.getSentBytesCount();

Если вы проверяете это периодически, вы будете наблюдать за прогрессом. Обязательно правильно обрабатывайте потоки, и все попытки / ловить охраняются. В частности, обработайте случай, когда Context больше не действителен (IllegalStateException), который происходит, когда Put отменен или завершен.

...