2012-02-29 13 views
19

Sono in difficoltà con il seguente problema: La mia app effettua una sequenza di richieste al server http utilizzando HttpClient. Io uso HttpPut per l'invio di dati al server. La prima richiesta va bene e velocemente, la seconda richiesta si blocca per 40 secondi e quindi si verifica un'eccezione Timeout di connessione. Sto cercando di riutilizzare il mio HttpClient e inviare una seconda richiesta attraverso la stessa istanza. Se creo un nuovo HttpClient insieme al nuovo ConnectionManager, allora tutto funziona correttamente.Android httpclient si blocca su seconda richiesta al server (timeout della connessione)

Perché sta succedendo? E come risolverlo e non creare ogni volta nuovi HttpClient?

Grazie in anticipo.

Qui è il mio codice: (se io commento readClient = newHttpClient (readClient) in doPut, allora il problema si pone

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

}

+0

Il server può essere chiudendo la connessione. Quali sono le intestazioni di risposta? –

+0

Anche il server non riceve la mia seconda richiesta –

+2

consumeContent() è la risposta, grazie per aver chiesto che –

risposta

13

suona strano, ma ho avuto lo stesso identico problema.. L'app su cui stavo lavorando richiedeva diverse richieste per scaricare un po 'di immagini in miniatura da visualizzare in un ListView e dopo la seconda si bloccava come se ci fosse un blocco morto nel codice HttpClient.

Lo strano correzione che ho trovato era da usare AndroidHttpClient invece di DefaultHttpClient. Non appena ho fatto questo, e ho provato un sacco di cose prima di percorrere questa strada, ha iniziato a funzionare bene. Ricordati di chiamare client.close() quando hai finito con la richiesta.

AndroidHttpClient è descritto nella documentazione come DefaultHttpClient con "impostazioni predefinite ragionevoli e schemi registrati per Android". Dato che questo è stato introdotto in api livello 8 (Android 2.2), ho scavato il codice sorgente per duplicare queste "impostazioni predefinite" in modo che potessi usarlo più indietro rispetto a quel livello API. Ecco il mio codice per duplicare le impostazioni predefinite e una classe di supporto con un metodo statico per la chiusura in sicurezza

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; 
    } 

} 

E in un'altra classe ...

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

Grazie mille, proverò questo metodo. Usi un'istanza per tutte le tue richieste dopo queste modifiche o chiami newInstance ogni volta che devi inviare i dati? E chiami sicuro Chiudi dopo CIASCUNA richiesta? –

+0

Sì ... nuova istanza ogni volta e sicura Chiudi ogni volta. Non mi accorgo di alcun impatto sulle prestazioni e questa app che stavo facendo al momento in cui ho avuto questo problema ha richiesto un sacco di richieste di assistenza. – Rich

+0

Se faccio lo stesso, chiudo e apro nuove istanze ogni volta, quindi il problema scompare. Ma ho letto nei documenti che è fortemente consigliato riutilizzare HttpClient + ConnManager e non crearlo per ogni connessione, che dovrebbe risparmiare un sacco di tempo e CPU ... Mi sbaglio? –

6

Ho lo stesso problema, quando eseguire più richieste in un ciclo.

È possibile risolverlo leggendo tutti di response.getEntity().

+0

Come si fa? – CACuzcatlan

+0

Si consiglia di leggere lo stream fino alla fine, ad esempio InputStream is = response.getEntity(); while (is.read()! = - 1); – wilddev

24

Suoni come voi non consumare l'entità dopo aver terminato la gestione della risposta. Assicurarsi che si inserisce il seguente codice nel blocco finally:

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

vi consiglio di leggere il HttpClient Tutorial.

+0

Questo ha risolto il mio problema, grazie davvero –

+2

Questa dovrebbe essere la risposta corretta! Problema risolto! – GMsoF

+0

Ottima risposta. Al punto e sostenuto con un tutorial. –

1

Ho avuto lo stesso problema. Sto consumando tutto il contenuto.

Quello che ho trovato è che se faccio una garbage collection dopo l'invio di una richiesta, tutto funziona senza dover chiudere e creare un nuovo AndroidHttpClient:

System.gc();

3

Ho pensato di approfondire le altre risposte. Ho anche avuto questo problema.Il problema era perché non stavo consumando contenuti.

Sembra che se non lo si fa, la connessione si terrà su di esso e non si è in grado di inviare una nuova richiesta con questa stessa connessione. Per me è stato un errore particolarmente difficile da individuare mentre stavo usando il BasicResponseHandler fornito in Android. Il codice è simile a questo ...

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

Quindi, se c'è una riga di stato sopra 300, non consumo il contenuto. E c'era del contenuto nel mio caso. Ho fatto la mia classe in questo modo ...

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

} 

Quindi, in ogni caso, consumare il contenuto!

+0

In realtà il codice di risposta potrebbe non essere rilevante; dovresti tentare di consumare il contenuto indipendentemente. Per esempio. Stavo scaricando le immagini, ma avrei ricevuto una risposta 404 E una "immagine di cortesia" come contenuto! Questo è anche il caso quando si ricevono 500 risposte, la maggior parte dei server preferisce fornire una "pagina" per ciò che conta come contenuto. –

1

E 'abbastanza per soluzione problema (ho avuto la stessa):

EntityUtils.consume(response.getEntity()); 

controllo Null eseguita all'interno di consumare

+2

non esiste un metodo EntityUtils.consume? Posso solo chiamare entity.consumeContent(); – AFD

1

Poiché molte di queste risposte sono vecchi e dipendono dal consumeContent() metodo ora depricated, mi ho pensato di rispondere con un'alternativa al problema di 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 

Ecco quello che si vede nella documentazione:

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() 
Problemi correlati