Не удается получить доступ к загрузке 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 вообще, потому что все данные записываются при выполнении запроса. Таким образом, нет никакого способа добиться прогресса.
Есть ли кто-нибудь, кто имеет какую-либо другую идею, как захватить процесс в многостраничной/форме загрузки?
Ответы
Ответ 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) { // сделать что-то для обновления пользовательского интерфейса.
}
}