Android網路編程(二)HttpClient與HttpURLConnection
前言
上一篇我們瞭解了HTTP協議原理,這一篇我們來講講Apache的HttpClient和Java的HttpURLConnection,這兩種都是我們平常請求網路會用到的。無論我們是自己封裝的網路請求類還是第三方的網路請求架構都離不開這兩個類庫。
1.HttpClient
Android SDK中包含了HttpClient,在Android6.0版本直接刪除了HttpClient類庫,如果仍想使用則解決方案是:
如果使用的是eclipse則在libs中加入org.apache.http.legacy.jar
這個jar包在:**sdk\platforms\android-23\optional目錄中(需要下載android
6.0的SDK) 如果使用的是android studio則 在相應的module下的build.gradle中加入:
android { useLibrary 'org.apache.http.legacy' }
HttpClient的GET請求
首先我們來用DefaultHttpClient類來執行個體化一個HttpClient,並配置好預設的請求參數:
//建立HttpClient private HttpClient createHttpClient() { HttpParams mDefaultHttpParams = new BasicHttpParams(); //設定連線逾時 HttpConnectionParams.setConnectionTimeout(mDefaultHttpParams, 15000); //佈建要求逾時 HttpConnectionParams.setSoTimeout(mDefaultHttpParams, 15000); HttpConnectionParams.setTcpNoDelay(mDefaultHttpParams, true); HttpProtocolParams.setVersion(mDefaultHttpParams, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(mDefaultHttpParams, HTTP.UTF_8); //持續握手 HttpProtocolParams.setUseExpectContinue(mDefaultHttpParams, true); HttpClient mHttpClient = new DefaultHttpClient(mDefaultHttpParams); return mHttpClient; }
接下來建立HttpGet和HttpClient,請求網路並得到HttpResponse,並對HttpResponse進行處理:
private void useHttpClientGet(String url) { HttpGet mHttpGet = new HttpGet(url); mHttpGet.addHeader("Connection", "Keep-Alive"); try { HttpClient mHttpClient = createHttpClient(); HttpResponse mHttpResponse = mHttpClient.execute(mHttpGet); HttpEntity mHttpEntity = mHttpResponse.getEntity(); int code = mHttpResponse.getStatusLine().getStatusCode(); if (null != mHttpEntity) { InputStream mInputStream = mHttpEntity.getContent(); String respose = converStreamToString(mInputStream); Log.i("wangshu", "請求狀態代碼:" + code + "\n請求結果:\n" + respose); mInputStream.close(); } } catch (IOException e) { e.printStackTrace(); } }
converStreamToString方法將請求結果轉換成String類型:
private String converStreamToString(InputStream is) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuffer sb = new StringBuffer(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } String respose = sb.toString(); return respose; }
最後我們開啟線程訪問百度:
new Thread(new Runnable() { @Override public void run() { useHttpClientGet("http://www.baidu.com"); } }).start();
請求的返回結果,請求狀態代碼為200,結果就是個html頁,這裡只截取了部分html代碼:
GET請求的參數暴露在URL中,這有些不大妥當,而且URL的長度也有限制:長度在2048字元之內,在HTTP 1.1後URL長度才沒有限制。一般情況下POST可以替代GET,接下來我們來看看HttpClient的POST請求。
HttpClient的POST請求
post請求和get類似就是需要配置要傳遞的參數:
private void useHttpClientPost(String url) { HttpPost mHttpPost = new HttpPost(url); mHttpPost.addHeader("Connection", "Keep-Alive"); try { HttpClient mHttpClient = createHttpClient(); List postParams = new ArrayList<>(); //要傳遞的參數 postParams.add(new BasicNameValuePair("username", "moon")); postParams.add(new BasicNameValuePair("password", "123")); mHttpPost.setEntity(new UrlEncodedFormEntity(postParams)); HttpResponse mHttpResponse = mHttpClient.execute(mHttpPost); HttpEntity mHttpEntity = mHttpResponse.getEntity(); int code = mHttpResponse.getStatusLine().getStatusCode(); if (null != mHttpEntity) { InputStream mInputStream = mHttpEntity.getContent(); String respose = converStreamToString(mInputStream); Log.i("wangshu", "請求狀態代碼:" + code + "\n請求結果:\n" + respose); mInputStream.close(); } } catch (IOException e) { e.printStackTrace(); } }
2.HttpURLConnection
Android 2.2版本之前,HttpURLConnection一直存在著一些令人厭煩的bug。比如說對一個可讀的InputStream調用close()方法時,就有可能會導致串連池失效了。那麼我們通常的解決辦法就是直接禁用掉串連池的功能:
private void disableConnectionReuseIfNecessary() { // 這是一個2.2版本之前的bug if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) { System.setProperty("http.keepAlive", "false"); }}
所以在Android 2.2版本以及之前的版本使用HttpClient是較好的選擇,而在Android 2.3版本及以後,HttpURLConnection則是最佳的選擇,它的API簡單,體積較小,因而非常適用於Android項目。壓縮和緩衝機制可以有效地減少網路訪問的流量,在提升速度和省電方面也起到了較大的作用。另外在Android 6.0版本中,HttpClient庫被移除了,HttpURLConnection則是以後我們唯一的選擇。
HttpURLConnection的POST請求
因為會了HttpURLConnection的POST請求那GET請求也就會了,所以我這裡只舉出POST的例子
首先我們建立一個UrlConnManager類,然后里面提供getHttpURLConnection()方法用於配置預設的參數並返回HttpURLConnection:
public static HttpURLConnection getHttpURLConnection(String url){ HttpURLConnection mHttpURLConnection=null; try { URL mUrl=new URL(url); mHttpURLConnection=(HttpURLConnection)mUrl.openConnection(); //設定連結逾時時間 mHttpURLConnection.setConnectTimeout(15000); //設定讀取逾時時間 mHttpURLConnection.setReadTimeout(15000); //佈建要求參數 mHttpURLConnection.setRequestMethod("POST"); //添加Header mHttpURLConnection.setRequestProperty("Connection","Keep-Alive"); //接收輸入資料流 mHttpURLConnection.setDoInput(true); //傳遞參數時需要開啟 mHttpURLConnection.setDoOutput(true); } catch (IOException e) { e.printStackTrace(); } return mHttpURLConnection ; }
因為我們要發送POST請求,所以在UrlConnManager類中再寫一個postParams()方法用來組織一下請求參數並將請求參數寫入到輸出資料流中:
public static void postParams(OutputStream output,ListparamsList) throws IOException{ StringBuilder mStringBuilder=new StringBuilder(); for (NameValuePair pair:paramsList){ if(!TextUtils.isEmpty(mStringBuilder)){ mStringBuilder.append("&"); } mStringBuilder.append(URLEncoder.encode(pair.getName(),"UTF-8")); mStringBuilder.append("="); mStringBuilder.append(URLEncoder.encode(pair.getValue(),"UTF-8")); } BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(output,"UTF-8")); writer.write(mStringBuilder.toString()); writer.flush(); writer.close(); }
接下來我們添加請求參數,調用postParams()方法將請求的參數組織好傳給HttpURLConnection的輸出資料流,請求串連並處理返回的結果:
private void useHttpUrlConnectionPost(String url) { InputStream mInputStream = null; HttpURLConnection mHttpURLConnection = UrlConnManager.getHttpURLConnection(url); try { List postParams = new ArrayList<>(); //要傳遞的參數 postParams.add(new BasicNameValuePair("username", "moon")); postParams.add(new BasicNameValuePair("password", "123")); UrlConnManager.postParams(mHttpURLConnection.getOutputStream(), postParams); mHttpURLConnection.connect(); mInputStream = mHttpURLConnection.getInputStream(); int code = mHttpURLConnection.getResponseCode(); String respose = converStreamToString(mInputStream); Log.i("wangshu", "請求狀態代碼:" + code + "\n請求結果:\n" + respose); mInputStream.close(); } catch (IOException e) { e.printStackTrace(); } }
最後開啟線程請求網路:
private void useHttpUrlConnectionGetThread() { new Thread(new Runnable() { @Override public void run() { useHttpUrlConnectionPost("http://www.baidu.com"); } }).start(); }
這裡我們仍舊請求百度,看看會發生什嗎?
mInputStream = mHttpURLConnection.getInputStream() 這句代碼報錯了,找不到檔案。開啟Fiddler來分析一下,不瞭解Fiddler和HTTP協議原理的請查看Android網路編程(一)HTTP協議原理這篇文章。
我們的請求報文:
看來請求報文沒有問題,再來看看響應報文:
報504錯誤,讀取響應的資料報錯,對於我們這次請求服務端不能返回完整的響應,返回的資料為0 bytes,所以mHttpURLConnection.getInputStream() 也讀不到服務端響應的輸入資料流。當然這次錯誤是正常的,百度沒理由處理我們的這次POST請求。
github源碼下載