Подтвердить что ты не робот

Android httpclient зависает во втором запросе на сервер (время ожидания подключения)

Я борюсь со следующей проблемой: Мое приложение выполняет последовательность запросов на HTTP-сервер, используя HttpClient. Я использую HttpPut для отправки данных на сервер. Первый запрос идет хорошо и быстро, второй запрос зависает в течение 40 секунд, а затем я улавливаю исключение синхронизации. Я пытаюсь повторно использовать мой HttpClient и отправлять второй запрос через один и тот же экземпляр. Если я создаю новый HttpClient вместе с новым ConnectionManager, тогда все будет хорошо.

Почему это происходит? И как его исправить и не создавать новый HttpClient каждый раз?

Спасибо заранее.

Вот мой код: (если я комментирую readClient = newHttpClient (readClient) в doPut, тогда возникает проблема.

public class WebTest
{
private HttpClient readClient;
private SchemeRegistry httpreg;
private HttpParams params;

private URI url; //http://my_site.net/data/

protected HttpClient newHttpClient(HttpClient oldClient)
{
    if(oldClient != null)
        oldClient.getConnectionManager().shutdown();

    ClientConnectionManager cm = new SingleClientConnManager(params, httpreg);
    return new DefaultHttpClient(cm, params);
}

protected String doPut(String data)
{
    //****************************
    //Every time we need to send data, we do new connection
    //with new ConnectionManager and close old one
    readClient = newHttpClient(readClient);

    //*****************************


    String responseS = null;
    HttpPut put = new HttpPut(url);
    try
    {
        HttpEntity entity = new StringEntity(data, "UTF-8");
        put.setEntity(entity);
        put.setHeader("Content-Type", "application/json; charset=utf-8");
        put.setHeader("Accept", "application/json");
        put.setHeader("User-Agent", "Apache-HttpClient/WebTest");

        responseS = readClient.execute(put, responseHandler);
    }
    catch(IOException exc)
    {
        //error handling here
    }
    return responseS;
}

public WebTest()
{
    httpreg = new SchemeRegistry();
    Scheme sch = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
    httpreg.register(sch);

    params = new BasicHttpParams();
    ConnPerRoute perRoute = new ConnPerRouteBean(10);
    ConnManagerParams.setMaxConnectionsPerRoute(params, perRoute);
    ConnManagerParams.setMaxTotalConnections(params, 50);
    ConnManagerParams.setTimeout(params, 15000);
    int timeoutConnection = 15000;
    HttpConnectionParams.setConnectionTimeout(params, timeoutConnection);
    // Set the default socket timeout (SO_TIMEOUT) 
    // in milliseconds which is the timeout for waiting for data.
    int timeoutSocket = 40000;
    HttpConnectionParams.setSoTimeout(params, timeoutSocket);
}

private ResponseHandler<String> responseHandler = new ResponseHandler<String>() 
{
    @Override
    public String handleResponse(HttpResponse response)
            throws ClientProtocolException, IOException
    {
        StatusLine statusLine = response.getStatusLine();
        if (statusLine.getStatusCode() >= 300) 
        {
            throw new HttpResponseException(statusLine.getStatusCode(),
                    statusLine.getReasonPhrase());
        }

        HttpEntity entity = response.getEntity();
        if(entity == null)
            return null;

        InputStream instream = entity.getContent();
        return this.toString(entity, instream, "UTF-8");
    }

    public String toString(
            final HttpEntity entity, 
            final InputStream instream, 
            final String defaultCharset) throws IOException, ParseException 
    {
        if (entity == null) 
        {
            throw new IllegalArgumentException("HTTP entity may not be null");
        }

        if (instream == null) 
        {
            return null;
        }
        if (entity.getContentLength() > Integer.MAX_VALUE) 
        {
            throw new IllegalArgumentException("HTTP entity too large to be buffered in memory");
        }
        int i = (int)entity.getContentLength();
        if (i < 0) 
        {
            i = 4096;
        }
        String charset = EntityUtils.getContentCharSet(entity);
        if (charset == null) 
        {
            charset = defaultCharset;
        }
        if (charset == null) 
        {
            charset = HTTP.DEFAULT_CONTENT_CHARSET;
        }

        Reader reader = new InputStreamReader(instream, charset);

        StringBuilder buffer=new StringBuilder(i);
        try 
        {
            char[] tmp = new char[1024];
            int l;
            while((l = reader.read(tmp)) != -1) 
            {
                buffer.append(tmp, 0, l);
            }
        } finally 
        {
            reader.close();
        }

        return buffer.toString();
    }
}; 

}

4b9b3361

Ответ 1

Звучит странно, но у меня была такая же проблема. Приложение, над которым я работал, выполняло несколько последовательных запросов для загрузки кучи миниатюр для отображения в ListView, а после второго - зависания, как будто в коде HttpClient была мертвая блокировка.

Странное исправление, которое я нашел, это использовать AndroidHttpClient вместо DefaultHttpClient. Как только я это сделал, и я пробовал много всего, прежде чем идти по этому маршруту, он начал работать нормально. Не забудьте вызвать client.close(), когда вы закончите с запросом.

AndroidHttpClient описан в документации как DefaultHttpClient с "разумными настройками по умолчанию и зарегистрированными схемами для Android". Поскольку это было введено на уровне 8 уровня (Android 2.2), я выкопал источник, чтобы дублировать эти "настройки по умолчанию", чтобы я мог использовать его дальше, чем этот уровень api. Вот мой код для дублирования значений по умолчанию и вспомогательного класса со статическим методом для безопасного его закрытия.

public class HttpClientProvider {

    // Default connection and socket timeout of 60 seconds. Tweak to taste.
    private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000;

    public static DefaultHttpClient newInstance(String userAgent)
    {
        HttpParams params = new BasicHttpParams();

        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
        HttpProtocolParams.setUseExpectContinue(params, true);

        HttpConnectionParams.setStaleCheckingEnabled(params, false);
        HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT);
        HttpConnectionParams.setSocketBufferSize(params, 8192);

        SchemeRegistry schReg = new SchemeRegistry();
        schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
        ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg);

        DefaultHttpClient client = new DefaultHttpClient(conMgr, params);

        return client;
    }

}

И в другом классе...

public static void safeClose(HttpClient client)
{
    if(client != null && client.getConnectionManager() != null)
    {
        client.getConnectionManager().shutdown();
    }
}

Ответ 2

Похоже, что вы не потребляете объект после завершения обработки ответа. Убедитесь, что вы поместите следующий код в блок finally:

if (httpEntity != null) {
    try {
        httpEntity.consumeContent();
    } catch (IOException e) {
        Log.e(TAG, "", e);
    }
}

Я предлагаю вам прочитать HttpClient Tutorial.

Ответ 3

У меня такая же проблема при выполнении нескольких запросов в цикле.

Вы можете решить это, прочитав все response.getEntity().

Ответ 4

Я подумал, что я расскажу о других ответах. Я тоже испытал эту проблему. Проблема заключалась в том, что я не потреблял контент.

Кажется, что если вы этого не сделаете, соединение будет продолжаться, и вы не сможете отправить новый запрос с этим же соединением. Для меня это была особенно трудная ошибка, когда я использовал BasicResponseHandler, предоставленный в android. Код выглядит следующим образом:

public String handleResponse(final HttpResponse response)
            throws HttpResponseException, IOException {
        StatusLine statusLine = response.getStatusLine();
        if (statusLine.getStatusCode() >= 300) {
            throw new HttpResponseException(statusLine.getStatusCode(),
                    statusLine.getReasonPhrase());
        }

        HttpEntity entity = response.getEntity();
       return entity == null ? null : EntityUtils.toString(entity);
    }

Итак, если есть строка состояния выше 300, я не потребляю контент. И в моем случае был контент. Я сделал свой собственный класс таким образом...

public class StringHandler implements ResponseHandler<String>{

    @Override
    public BufferedInputStream handleResponse(HttpResponse response) throws IOException {
    public String handleResponse(final HttpResponse response)
                throws HttpResponseException, IOException {
            StatusLine statusLine = response.getStatusLine();
           HttpEntity entity = response.getEntity();
            if (statusLine.getStatusCode() >= 300) {
                if (entity != null) {
                    entity.consumeContent();
                }
                throw new HttpResponseException(statusLine.getStatusCode(),
                        statusLine.getReasonPhrase());
            }


           return entity == null ? null : EntityUtils.toString(entity);
        }
    }

}

Таким образом, в любом случае, используйте контент!

Ответ 5

У меня была такая же проблема. Я потребляю весь контент.

То, что я нашел, - если я делаю сборку мусора после выдачи запроса, все работает без необходимости закрывать и создавать новый AndroidHttpClient:

System.gc();

Ответ 6

Достаточно для решения проблемы (у меня было то же самое):

EntityUtils.consume(response.getEntity());

Проверка нулей внутри потребляет

Ответ 7

Поскольку многие из этих ответов старые и зависят от теперь лишенного метода consumeContent(), я думал, что отвечу альтернативой проблеме Timeout waiting for connection from pool.

    HttpEntity someEntity =  response.getEntity();

    InputStream stream = someEntity.getContent();
    BufferedReader rd = new BufferedReader(new InputStreamReader(stream));

    StringBuffer result = new StringBuffer();
    String line = "";
    while ((line = rd.readLine()) != null) {
        result.append(line);
    }
    // On certain android OS levels / certain hardware, this is not enough.
    stream.close(); // This line is what is recommended in the documentation

Вот что он показывает в документации:

cz.msebera.android.httpclient.HttpEntity
@java.lang.Deprecated 
public abstract void consumeContent()
                            throws java.io.IOException
This method is deprecated since version 4.1. Please use standard java
convention to ensure resource deallocation by calling
InputStream.close() on the input stream returned by getContent()