web-dev-qa-db-ja.com

android httpclientはサーバーへの2回目のリクエストでハングします(接続がタイムアウトしました)

私は次の問題に苦しんでいます:私のアプリはHttpClientを使用してhttpサーバーに一連のリクエストを行います。サーバーにデータを送信するためにHttpPutを使用します。最初のリクエストはうまくいき、2番目のリクエストは40秒間ハングし、接続タイムアウトの例外が発生します。 HttpClientを再利用して、同じインスタンスを介して2番目のリクエストを送信しようとしています。新しいConnectionManagerと一緒に新しいHttpClientを作成すると、すべてが正常に機能します。

なんでこんなことが起こっているの?そして、それを修正し、毎回新しいHttpClientを作成しない方法は?

前もって感謝します。

これが私のコードです:(doPutでreadClient = newHttpClient(readClient)とコメントすると、問題が発生します。

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

}

19

奇妙に聞こえますが、まったく同じ問題がありました。私が取り組んでいたアプリは、一連のサムネイル画像をダウンロードしてListViewに表示するように連続してリクエストしていましたが、2回目以降は、HttpClientコードにデッドロックがあるかのようにハングしていました。

私が見つけた奇妙な修正は、DefaultHttpClientの代わりにAndroidHttpClientを使用することでした。これを行うとすぐに、このルートに進む前に多くのことを試しましたが、問題なく動作し始めました。リクエストが完了したら、client.close()を呼び出すことを忘れないでください。

AndroidHttpClientは、「Android用の合理的なデフォルト設定と登録済みスキーム」を備えたDefaultHttpClientとしてドキュメントに記載されています。これはAPIレベル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();
    }
}
13
Rich

応答の処理が終了した後は、エンティティを消費しないようです。次のコードをfinallyブロックに配置してください。

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

HttpClientチュートリアル を読むことをお勧めします。

24
neevek

ループで複数のリクエストを実行すると、同じ問題が発生します。

all of response.getEntity()を読むことで解決できます。

6
wilddev

私は他の答えについて詳しく説明しようと思いました。私もこの問題を経験しました。問題は、私がコンテンツを消費していなかったためです。

そうしないと、接続が保持され、同じ接続で新しいリクエストを送信できないようです。私にとっては、Androidで提供されている BasicResponseHandler を使用していたため、見つけるのが特に難しいバグでした。コードは次のようになります...

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

}

したがって、基本的にはいずれにせよコンテンツを消費します!

3
jiduvah

これらの答えの多くは古く、現在は非難されている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()
1
Dale

問題解決にはこれ​​で十分です(私も同じです):

EntityUtils.consume(response.getEntity());

消費内で実行されるヌルチェック

1
GKislin

私はこれと同じ問題を抱えています。私はすべてのコンテンツを消費しています。

私が見つけたのは、リクエストを発行した後にガベージコレクションを実行すると、新しいAndroidHttpClientを閉じて作成しなくてもすべてが機能することです。

System.gc();

1
Peter