HttpUrlConnection 多部分文件上传与progressBar

Posted

技术标签:

【中文标题】HttpUrlConnection 多部分文件上传与progressBar【英文标题】:HttpUrlConnection multipart file upload with progressBar 【发布时间】:2014-04-26 17:19:13 【问题描述】:

我想查看HttpUrlConnection 上传文件的进度。我怎么能做到这一点?我尝试在OutputStream 中写入数据时计算字节数,但这是错误的,因为只有在我调用conn.getInputStream() 时才会发生真正的上传,所以我需要以某种方式检查 inputStream。这是我的代码:

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();

  

【问题讨论】:

嘿嘿,一个小建议尝试使用 asynchttp 库,它简单实用。 @AshwinSAshok 有什么教程可以解决我的问题吗? loopj.com/android-async-http @AshwinSAshok 伙计,谢谢链接,但我无法在谷歌上搜索这个库的任何好的教程,如果你正在使用这个库,请给我一个例子吗? 当我将文件上传到服务器时,您知道如何制作相同但使用HttpURLConnection 吗? 【参考方案1】:

因为你必须处理上传,我想大部分时间都花在了entity.writeTo(os); 上。也许与服务器的第一次联系也需要一些时间(DNS 解析,SSL 握手,...)。您为“实际上传”设置的标记不正确 IMO。

现在取决于你的Multipart-library,你是否可以拦截writeTo。如果它既聪明又节省资源,它就会遍历各个部分并将内容逐一流式传输到输出流。如果不是,并且.build() 操作正在创建一个大胖byte[],那么您可以获取此数组,将其分块流式传输到服务器并告诉您的用户已经完成了多少百分比的上传。

从资源的角度来看,我宁愿不知道会发生什么。但是,如果反馈非常重要,并且电影只有几兆字节,您可以先将 Multipart-Entity 流式传输到ByteArrayOutputStream,然后将创建的字节数组的小块写入服务器,同时通知您的用户进步。以下代码未经验证或测试(您可以将其视为伪代码):

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();

更优雅的方法是编写一个拦截 OutputStream,它记录进度并将真正的写入操作委托给底层的“真实”OutputStream。

编辑

@whizzzkey 写道:

我已经重新检查了很多次 - entity.writeTo(os) 不做真正的上传,它会做 conn.getResponseCode()conn.getInputStream()

现在很清楚了。 HttpURLConnection 正在缓冲您的上传数据,因为它不知道内容长度。您已设置标题“内容长度”,但显然 HUC 会忽略此标题。你必须打电话

conn.setFixedLengthStreamingMode(entity.getContentLength());

那你最好去掉对conn.setRequestProperty("Content-length", entity.getContentLength() + "");的调用

在这种情况下,HUC 可以写入标头,entity.writeTo(os) 可以真正将数据流式传输到服务器。否则,当 HUC 知道将传输多少字节时,将发送缓冲数据。所以事实上,getInputStream() 告诉 HUC 你已经完成了,但是在真正读取响应之前,所有收集到的数据都必须发送到服务器。

我不建议更改您的代码,但对于那些不知道传输数据的确切大小(以字节为单位,而不是字符!!)的人来说,您可以告诉 HUC 它应该将数据传输到没有设置确切内容长度的块:

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

【讨论】:

谢谢你的回答,但你错了:大部分时间都花在了 entity.writeTo(os) 上。我已经启动了调试模式,大部分时间都花在“BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));”这一行上,所以问题是我应该在获取 InputStream 时检查进度 您的文件有多大,您的网络连接速度是多少?你的后端服务器是什么(RoR、ASPX、Java、node.js、php……)?您可以跟踪流量服务器端吗,例如使用wireshark 或tcpdump? 我正在发送一个 3mb 的文件,我的网络速度是 2Mb/s,后端服务器是 PHP。对不起,我无法追踪流量服务器端,因为我没有访问权限,你的意思是问题是来自服务器的长响应,而 entity.writeTo(os) 是真正的上传? 我相信:entity.writeTo(os) 是真正的上传。如果您增加文件大小和/或限制网络速度,您也应该意识到这一点。如果您观察到上传时的短延迟和服务器响应 (getInputStream) 之前的长延迟,则问题可能出在服务器端(PHP 脚本需要时间来处理您的文件)。 我已经重新检查了很多次 - entity.writeTo(os) 不会进行真正的上传,它会执行 conn.getResponseCode() 或 conn.getInputStream()【参考方案2】:

在您的 Activity 中修改此代码...

公共类 PublishPostToServer 扩展 AsyncTask 实现 ProgressListenerForPost

    public Context pContext;
    public long totalSize;
    private String response;

    public PublishPostToServer(Context context) 
        pContext = context;

    

    protected void onPreExecute() 
        showProgressDialog();
    

    @Override
    protected Boolean doInBackground(Void... params) 
        boolean success = true;
        try 
            response = NetworkAdaptor.getInstance()
                    .upLoadMultipartImageToServer(
                            "",
                            "",
                            "", this, this); // Add file path, Authkey, caption 

         catch (Exception e) 
            success = false;
        
        return success;
    

    @Override
    protected void onPostExecute(Boolean result) 
        super.onPostExecute(result);
        //validateResponse(result, response);
    

    @Override
    protected void onProgressUpdate(Integer... values) 

        try 
            if (mProgressDialog != null) 
                mProgressDialog.setProgress(values[0]);
            
         catch (Exception exception) 

        
    

    @Override
    public void transferred(long num) 
        publishProgress((int) ((num / (float) totalSize) * 100));
    



private void showProgressDialog() 

    try 
        String dialogMsg = "Uploading Image...";
        mProgressDialog = new ProgressDialog(this);
        mProgressDialog.setMessage(dialogMsg);
        mProgressDialog.setIndeterminate(false);
        mProgressDialog.setMax(100);
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setCancelable(false);
        mProgressDialog.show();
     catch (Exception exception) 

    

现在,创建一个 NetworkAdapter 类

public String upLoadMultipartImageToServer(String sourceFileUri, 字符串 auth_key,字符串标题,ProgressListenerForPost 监听器, PublishPostToServer asyncListiner) 字符串 upLoadServerUri = "" + "upload_image";

    HttpPost httppost = new HttpPost(upLoadServerUri);

    File file = new File(sourceFileUri);

    if (file.exists()) 

        FileBody filebodyVideo = new FileBody(file);
        CustomMultiPartEntity multipartEntity = new CustomMultiPartEntity(
                HttpMultipartMode.BROWSER_COMPATIBLE, listiner);
        try 
            multipartEntity.addPart("auth_key", new StringBody(auth_key));
            multipartEntity.addPart("caption", new StringBody(caption));
            multipartEntity.addPart("image", filebodyVideo);
            asyncListiner.totalSize = multipartEntity.getContentLength();
            httppost.setEntity(multipartEntity);

        

        catch (UnsupportedEncodingException e1) 
            // TODO Auto-generated catch block
            e1.printStackTrace();
        

        DefaultHttpClient mHttpClient = new DefaultHttpClient();
        String response = "";
        try 
            response = mHttpClient.execute(httppost,
                    new MovieUploadResponseHandler());
         catch (ClientProtocolException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (IOException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        

        return response;
     else 
        return null;
    

 

@SuppressWarnings("rawtypes")
private class MovieUploadResponseHandler implements ResponseHandler 

    @Override
    public Object handleResponse(HttpResponse response)
            throws ClientProtocolException, IOException 

        HttpEntity r_entity = response.getEntity();
        String responseString = EntityUtils.toString(r_entity);
        // DebugHelper.printData("UPLOAD", responseString);

        return responseString;
    



public static boolean isValidResponse(String resultData) 
    try 

     catch (Exception exception) 
        //DebugHelper.printException(exception);
    
    return true;


public String upLoadVideoToServer(String currentFilePath, String string,
        PublishPostToServer publishPostToServer,
        PublishPostToServer publishPostToServer2) 
    // TODO Auto-generated method stub
    return null;

【讨论】:

他要使用HttpUrlConnection。

以上是关于HttpUrlConnection 多部分文件上传与progressBar的主要内容,如果未能解决你的问题,请参考以下文章

多线程下载 HttpURLConnection

JAVA通过HttpURLConnection 上传和下载文件

Android端通过HttpURLConnection上传文件到server

Android端通过HttpURLConnection上传文件到服务器

文件上传---普通文件fileupload.jar和url文件httpUrlConnection

HttpUrlConnection上传文件