最近有一個專案需要重構網路部分程式碼,由於之前的網路部分都已經封裝好,直接呼叫介面就行,重構的時候才發現,好多東西已經忘了,現在給大家總結出來,有需要的朋友可以拿走,文章的最後會有demo工程。
HttpURLConnection
早些時候其實我們都習慣性使用HttpClient,但是後來Android6.0之後不再支援HttpClient,需要新增Apache的jar才行,所以,就有很多開發者放棄使用HttpClient了,HttpURLConnection畢竟是標準Java介面(java.net) ,適配性還是很強的。
準備工作
在開始使用之前,我們需要知道網路請求都需要一些什麼引數。這裡羅列一些常用的引數:
- url 請求的地址,這個不用說了,肯定是必須的
- 請求方式:GET POST還有DELETE,最常用的還是GET和POST
- 加密規則,這個當然是根據需要可有可無的
- header 請求頭
- 引數 需要傳遞的引數
- 檔案 你可能需要通過網路上傳一個檔案
知道了這些,我們可以自己定義一個介面:
public interface IRequest {
public String getBaseUrl();
public String getMethod();
public IEncrypt getEncrypt();
public HashMap<String, Object> getParam();
public Map<String, FilePair> getFilePair();
public Map<String, String> getHeaders();
}複製程式碼
其中FilePair是:
public class FilePair{
String mFileName;
byte[] mBinaryData;
public FilePair(String fileName, byte[] data) {
this.mFileName = fileName;
this.mBinaryData = data;
}
}複製程式碼
構建這個類,是為了上傳檔案的時候使用方便。
有了這個介面,我們進行網路請求只需要傳遞這個介面即可,如果有新的引數,只需要增加介面中的方法即可,不需要改變網路核心的程式碼。
GET請求
get是用於資訊獲取的,就是說,它僅僅是獲取資源資訊,就像資料庫查詢一樣,不會修改,增加資料,不會影響資源的狀態。
他的請求方式是將引數拼接在url中的,比如你請求的地址是http://xxx
,引數是name = aa
,那麼拼接後應該是http://xxx?name=aa
所以我們可以這樣處理:
public static String get(IRequest request) {
InputStream inputStream = null;
HttpURLConnection httpURLConnection = null;
try {
URL url = new URL(buildGetUrl(request.getBaseUrl(), request.getParam(), request.getEncrypt()));
openUrlConnection(url,httpURLConnection);
normalSetting(httpURLConnection, Method.GET, request.getHeaders());
if (httpURLConnection == null) {
return null;
}
int responseCode = httpURLConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
inputStream = httpURLConnection.getInputStream();
String contentEncoding = httpURLConnection.getContentEncoding();
InputStream stream = null;
try {
stream = wrapStream(contentEncoding, inputStream);
String data = convertStreamToString(stream);
return data;
} catch (IOException e) {
return "";
} finally {
closeQuietly(stream);
}
}
return null;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}複製程式碼
首先需要根據引數拼接url:
private static String buildGetUrl(String urlPath, Map<String, Object> params, IEncrypt encrypt) {
if (TextUtils.isEmpty(urlPath) || params == null || params.size() == 0) {
return urlPath;
}
if (!urlPath.endsWith("?")) {
urlPath += "?";
}
String paramsStr = buildGetParams(params);
if (encrypt != null) {
paramsStr = encrypt.encrypt(urlPath, params);
}
StringBuilder sbUrl = new StringBuilder(urlPath);
sbUrl.append(paramsStr);
return sbUrl.toString();
}
private static String buildGetParams(Map<String, Object> params) {
StringBuilder sb = new StringBuilder();
Set<String> keys = params.keySet();
for (String key : keys) {
if (params.get(key) == null) {
continue;
}
sb = sb.append(key + "=" + URLEncoder.encode(params.get(key).toString()) + "&");
}
String paramsStr = sb.substring(0, sb.length() - 1).toString();
return paramsStr;
}複製程式碼
這裡可以看出可以根據encrypt進行加密,encrypt是實現的加密和解密介面:
public interface IEncrypt {
public String encrypt(String src);
public String dencrypt(String src);
}複製程式碼
加密之後,通過HttpURLConnection進行請求即可。
如果不需要加密,可以將這個引數設定為空,或者直接實現,返回原字串即可。
httpURLConnection.getResponseCode()是返回的響應碼,當為200時是標誌請求成功了,這裡需要注意的是如果返回301,或者是302,是由於連結重定向的問題造成的,我們可以通過String location =httpURLConnection.getHeaderField("Location");
獲取重定向的網址進行重新請求。其中有個normalSetting,這個我們放在後面說明。
POST
POST表示可能修改變伺服器上的資源的請求,比如我們發一個帖子到伺服器,這時候就用到了post請求,他會改變伺服器中的儲存資源。
POST 提交的資料必須放在訊息主體(entity-body)中,但協議並沒有規定資料必須使用什麼編碼方式。實際上,開發者完全可以自己決定訊息主體的格式,只要最後傳送的 HTTP 請求滿足上面的格式就可以。 所以我們必須告訴服務端你是用的什麼編碼方式。服務端通常是根據請求頭(headers)中的 Content-Type 欄位來獲知請求中的訊息主體是用何種方式編碼,再對主體進行解析。
application/x-www-form-urlencoded
這應該是最常見的 POST 提交資料的方式了。瀏覽器的原生 form 表單,如果不設定 enctype 屬性,那麼最終就會以 application/x-www-form-urlencoded 方式提交資料。請求類似於下面這樣(無關的請求頭在本文中都省略掉了):
POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3複製程式碼
我們需要做的是
Content-Type 被指定為 application/x-www-form-urlencoded
其次,提交的資料按照 key1=val1&key2=val2 的方式進行編碼,key 和 val 都進行了 URL 轉碼。程式碼如下:
httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
Uri.Builder builder = new Uri.Builder();
builder.appendQueryParameter("content", request.getMessage());
String query = builder.build().getEncodedQuery();
outputStream = new DataOutputStream(httpURLConnection.getOutputStream());
outputStream.write(query.getBytes());複製程式碼
multipart/form-data
這又是一個常見的 POST 資料提交的方式。我們使用表單上傳檔案時,必須讓 form 的 enctyped 等於這個值。直接來看一個請求示例:
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----xxxxx
------xxxxx
Content-Disposition: form-data; name="text"
title
------xxxxx
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
PNG ... content of chrome.png ...
------xxxxx--複製程式碼
首先生成了一個 boundary 用於分割不同的欄位,為了避免與正文內容重複,boundary 很長很複雜。然後 Content-Type 裡指明瞭資料是以 mutipart/form-data 來編碼,本次請求的 boundary 是什麼內容。訊息主體裡按照欄位個數又分為多個結構類似的部分,每部分都是以 --boundary 開始,緊接著內容描述資訊,然後是回車,最後是欄位具體內容(文字或二進位制)。如果傳輸的是檔案,還要包含檔名和檔案型別資訊。訊息主體最後以 --boundary-- 標示結束
看下程式碼:
httpURLConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
outputStream = httpURLConnection.getOutputStream();
addBodyParams(request.getParam(),request.getFilePair(), outputStream, boundary);複製程式碼
其中寫入資料的方法較為繁瑣:
private static void addBodyParams(HashMap<String, Object> map, Map<String, FilePair> filePair, OutputStream outputStream, String boundary) throws IOException {
boolean didWriteData = false;
StringBuilder stringBuilder = new StringBuilder();
Map<String, Object> bodyPair =map;
Set<String> keys = bodyPair.keySet();
for (String key : keys) {
if (bodyPair.get(key) != null) {
addFormField(stringBuilder, key, bodyPair.get(key).toString(), boundary);
}
}
if (stringBuilder.length() > 0) {
didWriteData = true;
outputStream = new DataOutputStream(outputStream);
outputStream.write(stringBuilder.toString().getBytes());
}
// upload files like POST files to server
if (filePair != null && filePair.size() > 0) {
Set<String> fileKeys = filePair.keySet();
for (String key : fileKeys) {
FilePair pair = filePair.get(key);
byte[] data = pair.mBinaryData;
if (data == null || data.length < 1) {
continue;
} else {
didWriteData = true;
addFilePart(pair.mFileName, data, boundary, outputStream);
}
}
}
if (didWriteData) {
finishWrite(outputStream, boundary);
}
}
private static void addFormField(StringBuilder writer, final String name, final String value, String boundary) {
writer.append("--").append(boundary).append(END)
.append("Content-Disposition: form-data; name=\"").append(name)
.append("\"").append(END)
.append("Content-Type: text/plain; charset=").append("UTF-8")
.append(END).append(END).append(value).append(END);
}
private static void addFilePart(final String fieldName, byte[] data, String boundary, OutputStream outputStream)
throws IOException {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("--").append(boundary).append(END)
.append("Content-Disposition: form-data; name=\"")
.append("pic").append("\"; filename=\"").append(fieldName)
.append("\"").append(END).append("Content-Type: ")
.append("application/octet-stream").append(END)
.append("Content-Transfer-Encoding: binary").append(END)
.append(END);
outputStream.write(stringBuilder.toString().getBytes());
outputStream.write(data);
outputStream.write(END.getBytes());
}複製程式碼
其它
除了上面提到過的兩種方式,還有application/json 以及text/xml ,這兩種在移動端開發很少使用,不再過多介紹。
post程式碼
public static String post(IRequest request) {
String boundary = UUID.randomUUID().toString();
HttpURLConnection httpURLConnection = null;
OutputStream outputStream = null;
InputStream inputStream = null;
URL url = null;
try {
url = new URL(request.getBaseUrl());
openUrlConnection(url,httpURLConnection);
normalSetting(httpURLConnection,Method.POST,request.getHeaders());
if (request.getParam() != null && request.getParam().size() > 0) {
httpURLConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
outputStream = httpURLConnection.getOutputStream();
addBodyParams(request.getParam(),request.getFilePair(), outputStream, boundary);
} else {
httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
Uri.Builder builder = new Uri.Builder();
builder.appendQueryParameter("content", request.getMessage());
String query = builder.build().getEncodedQuery();
outputStream = new DataOutputStream(httpURLConnection.getOutputStream());
outputStream.write(query.getBytes());
}
outputStream.flush();
int responseCode = httpURLConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
inputStream = httpURLConnection.getInputStream();
String contentEncoding = httpURLConnection.getContentEncoding();
InputStream stream = wrapStream(contentEncoding, inputStream);
String data = convertStreamToString(stream);
return data;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}複製程式碼
通用配置介紹
private static void normalSetting(HttpURLConnection urlConnection, Method method, Map<String, String> mHeaders) throws ProtocolException {
urlConnection.setConnectTimeout(connectionTimeOut);
urlConnection.setReadTimeout(readSocketTimeOut);
urlConnection.setRequestMethod(method.toString());
if (method == Method.GET) {
urlConnection.setRequestProperty("Accept-Encoding", "gzip");
if (mHeaders != null && mHeaders.size() > 0) {
Set<String> stringKeys = mHeaders.keySet();
for (String key : stringKeys) {
urlConnection.setRequestProperty(key, mHeaders.get(key));
}
}
} else if (method == Method.POST) {
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
}
}複製程式碼
其中
- setConnectTimeout:設定連線主機超時(單位:毫秒)
- setReadTimeout:設定從主機讀取資料超時(單位:毫秒)
- Accept-Encoding HTTP Header中Accept-Encoding 是瀏覽器發給伺服器,宣告瀏覽器支援的編碼型別
- setDoOutput(false);以後就可以使用 httpURLConnection.getOutputStream().write()
- setDoInput(true);以後就可以使用 httpURLConnection.getInputStream().read();
參考demo
這個demo是我根據自己專案中用到的進行整理的,可能有些情況考慮的不是很全面,但是基本思路就是這個樣子,用到的同學可以參考:
DEMO
我的公眾號: