閱讀153 返回首頁    go 阿裏雲 go 技術社區[雲棲]


使用單例模式實現自己的HttpClient工具類

引子


在Android開發中我們經常會用到網絡連接功能與服務器進行數據的交互,為此Android的SDK提供了Apache的HttpClient來方便我們使用各種Http服務。你可以把HttpClient想象成一個瀏覽器,通過它的API我們可以很方便的發出GET,POST請求(當然它的功能遠不止這些)。


比如你隻需以下幾行代碼就能發出一個簡單的GET請求並打印響應結果:

try {
        // 創建一個默認的HttpClient
        HttpClient httpclient =new DefaultHttpClient();
        // 創建一個GET請求
        HttpGet request =new HttpGet("www.google.com");
        // 發送GET請求,並將響應內容轉換成字符串
        String response = httpclient.execute(request, new BasicResponseHandler());
        Log.v("response text", response);
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

為什麼要使用單例HttpClient?


這隻是一段演示代碼,實際的項目中的請求與響應處理會複雜一些,並且還要考慮到代碼的容錯性,但是這並不是本篇的重點。注意代碼的第三行:

 HttpClient httpclient =new DefaultHttpClient();
在發出HTTP請求前,我們先創建了一個HttpClient對象。那麼,在實際項目中,我們很可能在多處需要進行HTTP通信,這時候我們不需要為每個請求都創建一個新的HttpClient。因為之前已經提到,HttpClient就像一個小型的瀏覽器,對於整個應用,我們隻需要一個HttpClient就夠了。看到這裏,一定有人心裏想,這有什麼難的,用單例啊!!就像這樣:

public class CustomerHttpClient {
    private static HttpClient customerHttpClient;
    
    private CustomerHttpClient() {
    }
    
    public static HttpClient getHttpClient() {
        if(null== customerHttpClient) {
            customerHttpClient =new DefaultHttpClient();
        }
        return customerHttpClient;
    }
}

那麼,哪裏不對勁呢?或者說做的還不夠完善呢?


多線程!試想,現在我們的應用程序使用同一個HttpClient來管理所有的Http請求,一旦出現並發請求,那麼一定會出現多線程的問題。這就好像我們的瀏覽器隻有一個標簽頁卻有多個用戶,A要上google,B要上baidu,這時瀏覽器就會忙不過來了。幸運的是,HttpClient提供了創建線程安全對象的API,幫助我們能很快地得到線程安全的“瀏覽器”。


解決多線程問題

publicclass CustomerHttpClient {
    privatestaticfinal String CHARSET = HTTP.UTF_8;
    privatestatic HttpClient customerHttpClient;

    private CustomerHttpClient() {
    }

    publicstaticsynchronized HttpClient getHttpClient() {
        if (null== customerHttpClient) {
            HttpParams params =new BasicHttpParams();
            // 設置一些基本參數
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params,
                    CHARSET);
            HttpProtocolParams.setUseExpectContinue(params, true);
            HttpProtocolParams
                    .setUserAgent(
                            params,
                            "Mozilla/5.0(Linux;U;Android 2.2.1;en-us;Nexus One Build.FRG83) "
                                    +"AppleWebKit/553.1(KHTML,like Gecko) Version/4.0 Mobile Safari/533.1");
            // 超時設置
/* 從連接池中取連接的超時時間 */
            ConnManagerParams.setTimeout(params, 1000);
            /* 連接超時 */
            HttpConnectionParams.setConnectionTimeout(params, 2000);
            /* 請求超時 */
            HttpConnectionParams.setSoTimeout(params, 4000);
            
            // 設置我們的HttpClient支持HTTP和HTTPS兩種模式
            SchemeRegistry schReg =new SchemeRegistry();
            schReg.register(new Scheme("http", PlainSocketFactory
                    .getSocketFactory(), 80));
            schReg.register(new Scheme("https", SSLSocketFactory
                    .getSocketFactory(), 443));

            // 使用線程安全的連接管理來創建HttpClient
            ClientConnectionManager conMgr =new ThreadSafeClientConnManager(
                    params, schReg);
            customerHttpClient =new DefaultHttpClient(conMgr, params);
        }
        return customerHttpClient;
    }
}

在上麵的getHttpClient()方法中,我們為HttpClient配置了一些基本參數和超時設置,然後使用ThreadSafeClientConnManager來創建線程安全的HttpClient。上麵的代碼提到了3種超時設置,比較容易搞混,故在此特作辨析。


HttpClient的3種超時說明


/* 從連接池中取連接的超時時間 */
ConnManagerParams.setTimeout(params, 1000);
/* 連接超時 */
HttpConnectionParams.setConnectionTimeout(params, 2000);
/* 請求超時 */
HttpConnectionParams.setSoTimeout(params, 4000);
 


第一行設置ConnectionPoolTimeout:這定義了從ConnectionManager管理的連接池中取出連接的超時時間,此處設置為1秒。


第二行設置ConnectionTimeout:  這定義了通過網絡與服務器建立連接的超時時間。Httpclient包中通過一個異步線程去創建與服務器的socket連接,這就是該socket連接的超時時間,此處設置為2秒。


第三行設置SocketTimeout:    這定義了Socket讀數據的超時時間,即從服務器獲取響應數據需要等待的時間,此處設置為4秒。



以上3種超時分別會拋出ConnectionPoolTimeoutException,ConnectionTimeoutException與SocketTimeoutException。 


封裝簡單的POST請求


有了單例的HttpClient對象,我們就可以把一些常用的發出GET和POST請求的代碼也封裝起來,寫進我們的工具類中了。目前我僅僅實現發出POST請求並返回響應字符串的方法以供大家參考。將以下代碼加入我們的CustomerHttpClient類中:

privatestaticfinal String TAG ="CustomerHttpClient";

publicstatic String post(String url, NameValuePair... params) {
        try {
            // 編碼參數
            List<NameValuePair> formparams =new ArrayList<NameValuePair>(); // 請求參數
for (NameValuePair p : params) {
                formparams.add(p);
            }
            UrlEncodedFormEntity entity =new UrlEncodedFormEntity(formparams,
                    CHARSET);
            // 創建POST請求
            HttpPost request =new HttpPost(url);
            request.setEntity(entity);
            // 發送請求
            HttpClient client = getHttpClient();
            HttpResponse response = client.execute(request);
            if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                thrownew RuntimeException("請求失敗");
            }
            HttpEntity resEntity =  response.getEntity();
            return (resEntity ==null) ?null : EntityUtils.toString(resEntity, CHARSET);
        } catch (UnsupportedEncodingException e) {
            Log.w(TAG, e.getMessage());
            returnnull;
        } catch (ClientProtocolException e) {
            Log.w(TAG, e.getMessage());
            returnnull;
        } catch (IOException e) {
            thrownew RuntimeException("連接失敗", e);
        }

    } 

使用我們的CustomerHttpClient工具類

現在,在整個項目中我們都能很方便的使用該工具類來進行網絡通信的業務代碼編寫了。下麵的代碼演示了如何使用username和password注冊一個賬戶並得到新賬戶ID。

final String url ="https://yourdomain/context/adduser";
    //準備數據
    NameValuePair param1 =new BasicNameValuePair("username", "張三");
    NameValuePair param2 =new BasicNameValuePair("password", "123456");
    int resultId =-1;
    try {
        // 使用工具類直接發出POST請求,服務器返回json數據,比如"{userid:12}"
        String response = CustomerHttpClient.post(url, param1, param2);
        JSONObject root =new JSONObject(response);
        resultId = Integer.parseInt(root.getString("userid"));
        Log.i(TAG, "新用戶ID:"+ resultId);
    } catch (RuntimeException e) {
        // 請求失敗或者連接失敗
        Log.w(TAG, e.getMessage());
        Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT);
    } catch (Exception e) {
        // JSon解析出錯
        Log.w(TAG, e.getMessage());
    }

結語


可以看到,使用工具類能大大提高在項目中編寫網絡通信代碼的效率。不過該工具類還有待完善,歡迎各位補充和矯正錯誤,希望最後能完成一個工具類作為使用HttpClient的最佳實踐。(完)

出處https://www.cnblogs.com/codingmyworld/archive/2011/08/17/2141706.html  

最後更新:2017-04-02 16:47:34

  上一篇:go 找不到請求的 .Net Framework Data Provider。可能沒有安裝.
  下一篇:go Android中的Button、ImageButton自定義點擊效果