Android httpclient文件上传数据损坏和超时问题

Posted

技术标签:

【中文标题】Android httpclient文件上传数据损坏和超时问题【英文标题】:Android httpclient file upload data corruption and timeout issues 【发布时间】:2011-06-21 06:47:28 【问题描述】:

我在 android 中上传图片时遇到问题。

我正在使用 apache httpmime 4.1 库 代码是这样的:

MultipartEntity reqEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);

reqEntity.addPart("image", new FileBody(new File(AndorraApplication.getPhotosPath() + "/" + entity.getFileName()), "image/jpeg"));
resp = NetworkUtils.sendHttpRequestMultipart(EXPORT_PHOTOS_URI, reqEntity);

NetworkUtils 类:

public class NetworkUtils 
    public static final int REGISTRATION_TIMEOUT = 3 * 1000; 
    public static final int WAIT_TIMEOUT = 5 * 1000;

    public static HttpResponse sendHttpRequestMultipart(String uri, MultipartEntity entity) 
        HttpClient mHttpClient = new DefaultHttpClient();
        final HttpParams params = mHttpClient.getParams();
        HttpConnectionParams.setConnectionTimeout(params, REGISTRATION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(params, WAIT_TIMEOUT);
        ConnManagerParams.setTimeout(params, WAIT_TIMEOUT);

        HttpPost post = new HttpPost(uri);
        post.addHeader(entity.getContentType());
        post.setEntity(entity);
        HttpResponse resp = mHttpClient.execute(post);
    

有时一切正常,但有时(尤其是在连接速度较慢的情况下)上传的图像非常损坏。示例在这里:http://pixelbirthcloud.com/574_orig.jpg

它不会抛出任何异常。上传文件的长度与原始文件相同。尝试将 mime 类型更改为 application/octet-stream 或完全删除它。尝试玩超时。还是一样的结果。最终用户几乎一直都在上传损坏的图像(尽管我只设法获得了 2 次 bronem 图像).. 图像大小起初为 2.5 兆,但后来我将其减小到 500-700 kb。虽然没有解决问题。

还没有尝试更改 apache 的库.. 可能是问题.. 但据我阅读网络,没有人使用 httpmime 库遇到过这种情况。

会是什么?我现在完全迷路了:(

另一个问题是超时有时不起作用。

当谈到这一行时: HttpResponse resp = mHttpClient.execute(post); 我禁用了 3g 连接,它只等待 17-20 分钟而不是 3 或 5 秒.. 然后才抛出异常。尝试了不同的方法。像这样:

        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
        HttpProtocolParams.setUseExpectContinue(params, false);  
        HttpConnectionParams.setConnectionTimeout(params, 10000);
        HttpConnectionParams.setSoTimeout(params, 10000);
        ConnManagerParams.setMaxTotalConnections(params, 5);
        ConnManagerParams.setTimeout(params, 30000);

        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http",PlainSocketFactory.getSocketFactory(), 80));
        registry.register(new Scheme("https",PlainSocketFactory.getSocketFactory(), 80));
        ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(params, registry);
        HttpClient httpclient = new DefaultHttpClient(manager, params);

但还是不行:)

【问题讨论】:

【参考方案1】:

查看我的图像上传器代码,它对我很有用 此类将文件上传到服务器,最后还要读取 XML 回复。 根据您的要求过滤代码..它对我来说非常顺利

package com.classifieds;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;


import android.util.Log;

public class Uploader 


    private String Tag = "UPLOADER";
    private String urlString ;//= "YOUR_ONLINE_php";
    HttpURLConnection conn;
    String exsistingFileName ;

    private void uploadImageData(String existingFileName , String urlString)
    
        String lineEnd = "\r\n";
        String twoHyphens = "--";
        String boundary = "*****";
        try 
            // ------------------ CLIENT REQUEST

            Log.e(Tag, "Inside second Method");

            FileInputStream fileInputStream = new FileInputStream(new File(
                    exsistingFileName));

            // open a URL connection to the Servlet

            URL url = new URL(urlString);

            // Open a HTTP connection to the URL

            conn = (HttpURLConnection) url.openConnection();

            // Allow Inputs
            conn.setDoInput(true);

            // Allow Outputs
            conn.setDoOutput(true);

            // Don't use a cached copy.
            conn.setUseCaches(false);

            // Use a post method.
            conn.setRequestMethod("POST");

            conn.setRequestProperty("Connection", "Keep-Alive");

            conn.setRequestProperty("Content-Type",
                    "multipart/form-data;boundary=" + boundary);

            DataOutputStream dos = new DataOutputStream(conn.getOutputStream());

            dos.writeBytes(twoHyphens + boundary + lineEnd);
            dos
                    .writeBytes("Content-Disposition: post-data; name=uploadedfile;filename="
                            + exsistingFileName + "" + lineEnd);
            dos.writeBytes(lineEnd);

            Log.v(Tag, "Headers are written");

            // create a buffer of maximum size

            int bytesAvailable = fileInputStream.available();
            int maxBufferSize = 1000;
            // int bufferSize = Math.min(bytesAvailable, maxBufferSize);
            byte[] buffer = new byte[bytesAvailable];

            // read file and write it into form...

            int bytesRead = fileInputStream.read(buffer, 0, bytesAvailable);

            while (bytesRead > 0) 
                dos.write(buffer, 0, bytesAvailable);
                bytesAvailable = fileInputStream.available();
                bytesAvailable = Math.min(bytesAvailable, maxBufferSize);
                bytesRead = fileInputStream.read(buffer, 0, bytesAvailable);
            

            // send multipart form data necesssary after file data...

            dos.writeBytes(lineEnd);
            dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

            // close streams
            Log.v(Tag, "File is written");
            fileInputStream.close();
            dos.flush();
            dos.close();

         catch (MalformedURLException ex) 
            Log.e(Tag, "error: " + ex.getMessage(), ex);
        

        catch (IOException ioe) 
            Log.e(Tag, "error: " + ioe.getMessage(), ioe);
        


        SAXParserFactory spf = SAXParserFactory.newInstance();
        SAXParser sp = null;
        try 
            sp = spf.newSAXParser();
         catch (ParserConfigurationException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (SAXException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        

        // Get the XMLReader of the SAXParser we created.
        XMLReader xr = null;
        try 
            xr = sp.getXMLReader();
         catch (SAXException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
        // Create a new ContentHandler and apply it to the XML-Reader
        MyExampleHandler1 myExampleHandler = new MyExampleHandler1();
        xr.setContentHandler(myExampleHandler);

        // Parse the xml-data from our URL. 
        try 
            xr.parse(new InputSource(conn.getInputStream()));
        //xr.parse(new InputSource(new java.io.FileInputStream(new java.io.File("login.xml")))); 
         catch (MalformedURLException e) 
            Log.d("Net Disconnected", "NetDisconeeted");
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (IOException e) 
            Log.d("Net Disconnected", "NetDisconeeted");
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (SAXException e) 
            Log.d("Net Disconnected", "NetDisconeeted");
            // TODO Auto-generated catch block
            e.printStackTrace();
        

        // Parsing has finished.

    

    public Uploader(String existingFileName, boolean isImageUploading , String urlString ) 

        this.exsistingFileName = existingFileName;
        this.urlString = urlString;

    

    class MyExampleHandler1 extends DefaultHandler
    
    // ===========================================================
    // Methods
    // ===========================================================

    @Override
    public void startDocument() throws SAXException 

    

    @Override
    public void endDocument() throws SAXException 
        // Nothing to do
    

    @Override
    public void startElement(String namespaceURI, String localName,
              String qName, Attributes atts) throws SAXException 


    

    /** Gets be called on closing tags like:
     * </tag> */
    @Override
    public void endElement(String namespaceURI, String localName, String qName)
              throws SAXException 


    

    /** Gets be called on the following structure:
     * <tag>characters</tag> */
    @Override
    public void characters(char ch[], int start, int length) 

     
    


【讨论】:

您将点击的在线脚本应该具有读取文件并将其保存在数据库中的代码 我猜可能最终会做出这样的事情......虽然它没有回答问题,但谢谢......之前在网上看到过这样的解决方案,但决定采用更方便的方式- 使用 httpcomponents.. 但由于某种原因失败:( 它是一个完整的类,我们只需要传递文件url和脚本的url。还有什么比这个更方便 httpmime 库。 =) 实际上更容易使用。基于此制作了我自己的课程。接受文件或文本参数...希望它会以我期望的方式工作=)谢谢 注意到一件事:连接越慢,损坏的图像越多【参考方案2】:

我上传的 80% 的文件都存在同样的损坏问题。模拟器并没有上传失败。损坏的文件比原始文件大 1k。然后我将输出流的缓冲区设置为 1 个字节,它开始工作没有问题。最后,我让它为 8 个字节并且没有更多的损坏问题。大约 80 或 50 的缓冲区,我不记得了,也失败了。不明白问题是什么,但我很高兴它以这种方式工作。这个页面非常鼓舞人心,谢谢。

【讨论】:

请问有什么代码吗?【参考方案3】:

好的。花了 2 天时间测试这个问题,发现如下: 在 android 上使用 tcpdump 时,数据没有损坏,但是 tcp 数据包大小为 1516,这很奇怪,因为正常的以太网数据包大小为 1500,而超过 1516 的所有数据都太大了。 我手动将 MTU 更改为 576(我相信它是 ppp 的标准,实际上是 3G)并且它运行完美! 150张图片中有150张正常上传!

但是,这并不能解决实际问题,因为我相信在非 root 设备上更改 mtu 是不可能的,并且您必须在每次重新启动设备时更改它(或每次打开界面时 -不确定,因为找不到通过 ifconfig 获取 MTU 值的方法)。但至少我知道问题出在哪里。 将http块大小设置为较小的值(尝试300字节)并没有影响它(我相信这是因为http标头本身太大了)......所以......所以什么都没有=)

将尝试将其发布到 google 上的 android-developer 组,但他们的审核速度太慢了...我们拭目以待...

【讨论】:

嗨,Alex,我想我遇到了与您类似的问题,但我没有像您那样使用 tcpdump 进行验证。我正在使用httpclient,我所做的是在客户端发送的数据上设置校验和,在服务器端,在我们遇到这个问题的情况下校验和是错误的。在我的情况下,上传的数据似乎只是缺少文件的一部分。其他时候一切正常。我在 android-developers 上看到了你的帖子,但看起来你没有找到解决方案。任何新的报告(哦,拜托!) 如果您缺少文件的某些部分,那么这是另一个问题。如果您使用 mediastore,您应该查看文件长度错误的 froyo 错误。原因是如果服务器上的文件长度不同,但仍然接收到文件,则说明content-length错误,否则会因文件长度不同而拒绝上传... 谢谢Alex,我们已经验证了app创建的数据是正确的,但是我们在服务器上收到的版本是错误的。文件大小不同。我们没有使用 mediastore,所以文件大小的 froyo 错误不是我们的问题,而是其他问题。验证这仅在 Android 2.2+ 上发生,但在一系列手机和运营商上发生,并且发生在我们数百名用户身上 我有一种强烈的感觉,这不是安卓问题。你如何检查android上的数据是否正确?运行 tcpdump 以查看设备的具体内容。这可能有3个问题:1)连接在过程中间意外断开。在这种情况下,您将截断数据。意味着服务器和客户端上的相同数据块之间没有区别。 2) 数据可能以非二进制模式传输.. 3) 您在 android 上的 IO 算法不准确。最好是检查 tcpdump。只有这样,您才能百分百确定。你可以使用 wireshark 来解析转储 感谢您的帮助 Alex,因为这可能与您遇到的问题不同。我同意这似乎不是一个 android 问题,因为宇宙中似乎没有其他人发生过这种情况。我们通过让一些有问题的用户将应用程序发送的数据文件通过电子邮件发送给我们,我们验证了应用程序中的文件是否正确,我们对其进行了检查,它们的格式正确,而我们在服务器上获得的版本是文件的不同和丢失的随机部分,但丢失的部分在文件的中间,这不仅仅是截断问题。

以上是关于Android httpclient文件上传数据损坏和超时问题的主要内容,如果未能解决你的问题,请参考以下文章

android的自带的httpClient 怎么上传文件

android的自带的httpClient 怎么上传文件

Android开发之httpclient文件上传实现

Android网络编程之使用HttpClient和MultipartEntityBuilder 批量同时上传文件和文字

Apache HttpClient 4.0 无法在 Android 上的套接字超时

如何使用 C# HttpClient PostAsync 显示上传进度