我正在开发一款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)当我将字节写入“dos”时,为什么不发生uplaod? 2)如何在上传过程中显示进度对话框时如何获取进度?
/编辑: 1)我在标题中设置了Content-Length,它稍微改变了问题,但是没有 解决方法: 现在,在将最后一个字节写入流之后上传整个内容。因此,这不会改变您无法获取进度的情况,因为同样,数据会立即写入。 2)我在Apache HttpClient v4中尝试了MultipartEntity。在那里你根本没有OutputStream,因为在执行请求时会写入所有数据。所以再一次,没有办法取得进展。
有没有人知道如何在多部分/表单上传中抓取进程?
答案 0 :(得分:20)
在过去的几天里我已经做了很多尝试,我想我对最初的问题有了答案:
使用HttpURLConnection获取进度是不可能的,因为Android中存在错误/异常行为: http://code.google.com/p/android/issues/detail?id=3164#c6
它将在Froyo之后修复,这不是人们可以等待的东西......
因此,我找到的唯一其他选项是使用Apache HttpClient。 answer linked by 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 kb(当我使用我的问题中描述的分段上传时为90 kb),更糟糕的是,已安装的应用程序大小为735 kb(约170 kb之前)。 那太可怕了。只有在上传过程中取得进展,应用程序大小现在才是以前的4倍。
答案 1 :(得分:1)
试一试。它有效。
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
答案 2 :(得分:0)
可以从DefaultHttpClient获取进度。它保持在行
的事实response = defaultHttpClient.execute( httpput, context );
直到发送完整数据,并不一定意味着您无法获取进度。也就是说,HttpContext在执行此行时会发生变化,因此如果您通过不同的Thread接近它,您可能会观察到其中的变化。你需要的是ConnAdapter。它嵌入了HttpConnectionMetrics
final AbstractClientConnAdapter connAdapter = (AbstractClientConnAdapter) context.getAttribute(ExecutionContext.HTTP_CONNECTION);
final HttpConnectionMetrics metrics = connAdapter.getMetrics();
int bytesSent = (int)metrics.getSentBytesCount();
如果您定期检查,您将观察进度。一定要正确处理线程,并保护所有try / catch。特别是,处理Context不再有效的情况(IllegalStateException),当Put被取消或完成时发生。
答案 3 :(得分:0)
改为使用WatchedInputStream。 它很好,很棒,而且易于使用。
watchedStream.setListener
watchedStream.setListener(new WatchedInputStream.Listener(){
public void notify(int read){ //做一些事情来更新UI } }