Подтвердить что ты не робот

Не удается получить доступ к загрузке http POST файлов (Android)

Я разрабатываю приложение для 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 вообще, потому что все данные записываются при выполнении запроса. Таким образом, нет никакого способа добиться прогресса.

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

4b9b3361

Ответ 1

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

Невозможно добиться прогресса, используя 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) и последнего ответа (by 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 kb (около 170 kb до). Это действительно ужасно. Только для достижения прогресса при загрузке размер приложения теперь больше, чем в 4 раза больше, чем раньше.

Ответ 2

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

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

Ответ 3

Можно получить прогресс от 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();

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

Ответ 4

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

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

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

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