首页 文章

无法抓取http POST文件上传进度(Android)

提问于
浏览
17

我正在开发一个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());

这需要几秒钟或几分钟,具体取决于文件的大小和Internet连接的速度 . 我现在的问题是:1)当我将字节写入“dos”时,为什么不发生uplaod? 2)如果一次发生所有事情,我怎样才能获取进度以便在上传过程中显示进度对话框?

/编辑:1)我在 Headers 中设置了Content-Length,它稍微改变了问题,但没有以任何方式解决它:现在整个内容在最后一个字节写入流后上传 . 因此,这不会改变您无法获取进度的情况,因为同样,数据会立即写入 . 2)我在Apache HttpClient v4中尝试了MultipartEntity . 在那里你根本没有OutputStream,因为在执行请求时会写入所有数据 . 再说一次,没有办法 grab 进步 .

有没有人有任何其他想法如何在多部分/表单上传中获取进程?

4 回答

  • 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倍 .

  • 0

    试试吧 . 有用 .

    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

    可以从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();
    

    如果定期检查,您将观察进度 . 一定要正确处理线程,并保护所有try / catch . 特别是,处理Context不再有效的情况(IllegalStateException),这在Put被取消或完成时发生 .

  • 1

    请改用WatchedInputStream . 它很好,很棒,而且易于使用 .

    • 使用WatchedInputStream来包装你的输入流 .

    • watchedStream.setListener

    watchedStream.setListener(new WatchedInputStream.Listener(){

    public void notify(int read){//做一些更新UI的事情 .
    }}

相关问题