HttpURLConnection的多部分文件上传与进度 [英] HttpUrlConnection multipart file upload with progressBar

查看:327
本文介绍了HttpURLConnection的多部分文件上传与进度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想通过的HttpURLConnection 来检查上传文件的进度。我怎样才能做到这一点?我试着在的OutputStream 写入数据时计算字节,但它是错误的,造成真正的上传情况只有当我打电话 conn.getInputStream(),所以我需要以某种方式来检查的InputStream。这是我的code:

 公共静态无效uploadMovie(最终的HashMap<字符串,字符串>数据源,最终OnLoadFinishedListener finishedListener,最终ProgressListener progressListener){
  如果(finishedListener!= NULL){
    新主题(新的Runnable(){
       公共无效的run(){
         尝试 {

              字符串边界= getMD5(dataSource.size()+将String.valueOf(System.currentTimeMillis的()));
              MultipartEntityBuilder multipartEntity = MultipartEntityBuilder.create();
              multipartEntity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
              multipartEntity.setCharset(Charset.forName(UTF-8));

              对于(字符串键:dataSource.keySet()){
                 如果(key.equals(MoviesFragmentAdd.USERFILE)){
                    FileBody的用户文件=新FileBody(新文件(dataSource.get(键)));
                    multipartEntity.addPart(键,的用户文件);
                    继续;
                 }
                 multipartEntity.addPart(重点,新StringBody(dataSource.get(键),ContentType.APPLICATION_JSON));
              }

              HttpEntity实体= multipartEntity.build();
              HttpURLConnection的康恩=(HttpsURLConnection)新的URL(URL_API +/视频/ addForm /)的openConnection()。
              conn.setUseCaches(假);
              conn.setDoOutput(真正的);
              conn.setDoInput(真正的);
              conn.setRequestMethod(POST);
              conn.setRequestProperty(接收字符集,UTF-8);
              conn.setRequestProperty(连接,保持活动);
              conn.setRequestProperty(缓存控制,无缓存);
              conn.setRequestProperty(内容类型,多部分/格式数据;边界=+界);
              conn.setRequestProperty(内容长度,entity.getContentLength()+);
              conn.setRequestProperty(entity.getContentType()的getName(),entity.getContentType()的getValue());

              OutputStream的OS = conn.getOutputStream();
              entity.writeTo(OS);
              os.close();

              //真正上传从这里开始 - >>

              的BufferedReader在=新的BufferedReader(新的InputStreamReader(conn.getInputStream()));

              //<<  - 

              JSONObject的要求=(的JSONObject)gparser.parse(in.readLine());
              如果(!request.GET中(错误)。getAsBoolean()){
              //做一点事
              }
              conn.disconnect();

           }赶上(例外五){
            e.printStackTrace();
           }
         }
    })。开始();

  }
}
 

解决方案

由于您必须处理上传,我想大部分时间都是在做拍摄时 entity.writeTo(OS); 。也许第一次接触到服务器需要一定的时间,以及(DNS解析,SSL握手,...)。你的真正的上传设置的标志是不正确的海事组织。

现在就看你的多部图书馆,您是否能拦截的writeTo 。如果是聪明和资源节约型,它遍历部分和流的内容一个接一个输出流。如果没有,而 .build()操作创建一个大胖子字节[] ,那么你可以利用这个阵列,在块流式处理到服务器,并告诉用户如何上传百分之多少已经完成。

从资源角度看,我倒是preFER没有真正知道会发生什么。但是,如果反馈是重要的,如果电影只有几兆大小,你可以先流多部分实体为 ByteArrayOutputStream ,然后写的创建小块字节数组服务器,同时通知你有关进度的用户。下面code未验证或测试(你可以把它看成伪code):

ByteArrayOutputStream BAOS =新ByteArrayOutputStream(); entity.writeTo(BAOS); baos.close(); byte []的有效载荷= baos.toByteArray(); BAOS = NULL; OutputStream的OS = conn.getOutputStream(); INT totalSize = payload.length; INT bytesTransferred = 0; INT CHUNKSIZE = 2000; 而(bytesTransferred< totalSize){     INT nextChunkSize = totalSize - bytesTransferred;     如果(nextChunkSize> CHUNKSIZE){         nextChunkSize = CHUNKSIZE;     }     os.write(有效载荷,bytesTransferred,nextChunkSize); // TODO检查的结果!     bytesTransferred + = nextChunkSize;     //在这里你可以调用的更新进步的方法     //一定把它包起来,以便用户界面,更新在主线程中完成的!     updateProgressInfo(100 * bytesTransferred / totalSize); } os.close();

有一个更优雅的方式是写一个拦截的OutputStream哪些寄存器的进步和代表真正的写操作垫层真正的OutputStream。

修改

@whizzzkey写道:

  

我已经重新检查了很多次 - entity.writeTo(OS)不会做一个真正的上传,但它确实连接。 GETRESPONSE code() conn.getInputStream()

现在很清楚。 的HttpURLConnection 是缓冲你的上传数据,因为它不知道内容长度。你设置了头内容长度,但oviously这是由HUC忽略。你要叫

conn.setFixedLengthStreamingMode(entity.getContentLength());

那你应该更好地删除呼叫 conn.setRequestProperty(内容长度,entity.getContentLength()+);

在这种情况下,HUC可以写出标题和 entity.writeTo(OS)真的可以将数据传输到服务器。否则当HUC知道有多少个字节将被传送的缓冲的数据被发送。所以,事实上,的getInputStream()告诉HUC,你就完蛋了,但在真正读取响应,所有收集到的数据发送到服务器。

我不会建议改变你的code,但对于那些你们谁不知道传送的数据的确切大小(以字节为单位,而不是以字符!),你可以告诉HUC它应该转移没有设定确切的内容长度的块中的数据:

conn.setChunkedStreamingMode(-1); //使用默认的块大小

I want to check progress of uploading file by HttpUrlConnection. How I can do this? I've tried to calculate bytes when writing data in OutputStream but it's wrong, cause real uploading happens only when I call conn.getInputStream(), so I need somehow to check inputStream. Here is my code:

public static void uploadMovie(final HashMap<String, String> dataSource, final OnLoadFinishedListener finishedListener, final ProgressListener progressListener) {
  if (finishedListener != null) {
    new Thread(new Runnable() {
       public void run() {
         try {

              String boundary = getMD5(dataSource.size()+String.valueOf(System.currentTimeMillis()));
              MultipartEntityBuilder multipartEntity = MultipartEntityBuilder.create();
              multipartEntity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);    
              multipartEntity.setCharset(Charset.forName("UTF-8"));

              for (String key : dataSource.keySet()) {
                 if (key.equals(MoviesFragmentAdd.USERFILE)) {
                    FileBody  userFile = new FileBody(new File(dataSource.get(key)));
                    multipartEntity.addPart(key, userFile);
                    continue;
                 }
                 multipartEntity.addPart(key, new StringBody(dataSource.get(key),ContentType.APPLICATION_JSON));
              }

              HttpEntity entity = multipartEntity.build();
              HttpURLConnection conn = (HttpsURLConnection) new URL(URL_API + "/video/addForm/").openConnection();
              conn.setUseCaches(false);
              conn.setDoOutput(true);
              conn.setDoInput(true);
              conn.setRequestMethod("POST");
              conn.setRequestProperty("Accept-Charset", "UTF-8");
              conn.setRequestProperty("Connection", "Keep-Alive");
              conn.setRequestProperty("Cache-Control", "no-cache");
              conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
              conn.setRequestProperty("Content-length", entity.getContentLength() + "");
              conn.setRequestProperty(entity.getContentType().getName(),entity.getContentType().getValue());

              OutputStream os = conn.getOutputStream();
              entity.writeTo(os);
              os.close();

              //Real upload starting here -->>

              BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));

              //<<--

              JsonObject request = (JsonObject) gparser.parse(in.readLine());
              if (!request.get("error").getAsBoolean()) {
              //do something
              }
              conn.disconnect(); 

           } catch (Exception e) {
            e.printStackTrace();
           }
         }
    }).start();

  }
}

解决方案

Because you have to deal with upload, I'd suppose most time is taken when doing entity.writeTo(os);. Maybe the first contact to the server takes some time as well (DNS resolution, SSL-handshake, ...). The markers you set for "the real upload" are not correct IMO.

Now it depends on your Multipart-library, whether you can intercept writeTo. If it is clever and resource-efficient, it's iterating over the parts and streams the content one-by-one to the output stream. If not, and the .build() operation is creating a big fat byte[], then you could take this array, stream it in chunks to the server and tell your user how many percent of the upload is already done.

From a resource perspective I'd prefer not really knowing what happens. But if feedback is that important and if the movies are only a few megabytes in size, you could stream the Multipart-Entity first to a ByteArrayOutputStream and then write little chunks of the created byte-array to the server while notifying your user about progress. The following code is not validated or tested (you can see it as pseudo-code):

ByteArrayOutputStream baos = new ByteArrayOutputStream();
entity.writeTo(baos);
baos.close();
byte[] payload = baos.toByteArray();
baos = null;

OutputStream os = conn.getOutputStream();

int totalSize = payload.length;
int bytesTransferred = 0;
int chunkSize = 2000;

while (bytesTransferred < totalSize) {
    int nextChunkSize = totalSize - bytesTransferred;
    if (nextChunkSize > chunkSize) {
        nextChunkSize = chunkSize;
    }
    os.write(payload, bytesTransferred, nextChunkSize); // TODO check outcome!
    bytesTransferred += nextChunkSize;

    // Here you can call the method which updates progress
    // be sure to wrap it so UI-updates are done on the main thread!
    updateProgressInfo(100 * bytesTransferred / totalSize);
}
os.close();

A more elegant way would be to write an intercepting OutputStream which registers progress and delegates the real write-operations to the underlaying "real" OutputStream.

Edit

@whizzzkey wrote:

I've re-checked it many times - entity.writeTo(os) DOESN'T do a real upload, it does conn.getResponseCode() or conn.getInputStream()

Now it's clear. HttpURLConnection is buffering your upload data, because it doesn't know the content-length. You've set the header 'Content-length', but oviously this is ignored by HUC. You have to call

conn.setFixedLengthStreamingMode(entity.getContentLength());

Then you should better remove the call to conn.setRequestProperty("Content-length", entity.getContentLength() + "");

In this case, HUC can write the headers and entity.writeTo(os) can really stream the data to the server. Otherwise the buffered data is sent when HUC knows how many bytes will be transferred. So in fact, getInputStream() tells HUC that you're finished, but before really reading the response, all the collected data has to be sent to the server.

I wouldn't recommend changing your code, but for those of you who don't know the exact size of the transferred data (in bytes, not characters!!), you can tell HUC that it should transfer the data in chunks without setting the exact content-length:

conn.setChunkedStreamingMode(-1); // use default chunk size

这篇关于HttpURLConnection的多部分文件上传与进度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆