Android HTTP協議請求網路(三)之HttpURLConnection方式
Android HTTP協議請求網路(二)之HttpClient方式
githup練習demo地址:https://github.com/aiyangtianci/NetworkRequestDemo
一、介紹
在Android API Level 9(Android 2.2)之前只能使用DefaultHttpClient類傳送http請求。DefaultHttpClient是Apache用於傳送http請求的客戶端,其提供了強大的API支援,而且基本沒有什麼bug,但是由於其太過複雜,Android團隊在保持向後相容的情況下,很難對DefaultHttpClient進行增強。為此,Android團隊從Android API Level 9開始自己實現了一個傳送http請求的客戶端類——–HttpURLConnection。
相比於DefaultHttpClient,HttpURLConnection比較輕量級,雖然功能沒有DefaultHttpClient那麼強大,但是能夠滿足大部分的需求,所以Android推薦使用HttpURLConnection代替DefaultHttpClient,並不強制使用HttpURLConnection。
但從Android API Level 23(Android 6.0)開始,不能再在Android中使用DefaultHttpClient,強制使用HttpURLConnection。
二、Get方式:
實現流程步驟:
第一步:例項化URL物件。
第二步:例項化HttpUrlConnection物件。
第三步:設定請求連線屬性,傳遞引數等。
第四步:獲取返回碼判斷是否連結成功。
第五步:讀取輸入流。
第六步:關閉連結。
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL("http://192.168.23.1:8080/TestProject/GetTest");
connection = (HttpURLConnection) url.openConnection();。
// 設定請求方法,預設是GET
connection.setRequestMethod("GET");
// 設定字符集
connection.setRequestProperty("Charset", "UTF-8");
// 設定檔案型別
connection.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");
// 設定請求引數,可通過Servlet的getHeader()獲取
connection.setRequestProperty("Cookie", "AppName=" + URLEncoder.encode("你好", "UTF-8"));
// 設定自定義引數
connection.setRequestProperty("MyProperty", "this is me!");
if(connection.getResponseCode() == 200){
InputStream is = connection.getInputStream();
result = StringStreamUtil.inputStreamToString(is);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(connection != null){
connection.disconnect();
}
}
三、Post方式:
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL("http://192.168.23.1:8080/TestProject/PostTest");
connection = (HttpURLConnection) url.openConnection();
// 設定請求方式
connection.setRequestMethod("POST");
// 設定編碼格式
connection.setRequestProperty("Charset", "UTF-8");
// 傳遞自定義引數
connection.setRequestProperty("MyProperty", "this is me!");
// 設定容許輸出
connection.setDoOutput(true);
// 上傳一張圖片|上傳引數: byte[] requestBody = new String("name=孫群&age=27").getBytes("UTF-8");
FileInputStream file = new FileInputStream(Environment.getExternalStorageDirectory().getPath()
+ "/Pictures/Screenshots/Screenshot_2015-12-19-08-40-18.png");
OutputStream os = connection.getOutputStream();
int count = 0;
while((count=file.read()) != -1){
os.write(count);
}
os.flush();
os.close();
// 獲取返回資料
if(connection.getResponseCode() == 200){
InputStream is = connection.getInputStream();
result = StringStreamUtil.inputStreamToString(is);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(connection!=null){
connection.disconnect();
}
}
};
四、上傳APK包大檔案:
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL("http://192.168.23.1:8080/TestProject/FileTest");
connection = (HttpURLConnection) url.openConnection();
// 設定每次傳輸的流大小,可以有效防止手機因為記憶體不足崩潰
// 此方法用於在預先不知道內容長度時啟用沒有進行內部緩衝的 HTTP請求正文的流。
connection.setChunkedStreamingMode(51200); // 128K
// 不使用快取
connection.setUseCaches(false);
// 設定請求方式
connection.setRequestMethod("POST");
// 設定編碼格式
connection.setRequestProperty("Charset", "UTF-8");
// 設定容許輸出
connection.setDoOutput(true);
// 上傳檔案
FileInputStream file = new FileInputStream(Environment.getExternalStorageDirectory().getPath()
+ "/aaaaa/baidu_map.apk");
OutputStream os = connection.getOutputStream();
byte[] b = new byte[1024];
int count = 0;
while((count = file.read(b)) != -1){
os.write(b, 0, count);
}
os.flush();
os.close();
// 獲取返回資料
if(connection.getResponseCode() == 200){
InputStream is = connection.getInputStream();
result = StringStreamUtil.inputStreamToString(is);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(connection != null){
connection.disconnect();
}
}
};
五、檔案斷點上傳:
public class MoreUploadActivity extends Activity {
private TextView mTvMsg;
private String result = "";
private long start = 0; // 開始讀取的位置
private long stop = 1024 * 1024; // 結束讀取的位置
private int times = 0; //讀取次數
private long fileSize = 0; //檔案大小
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_times_upload);
initView();
}
private void initView(){
mTvMsg = (TextView) findViewById(R.id.tv_upload);
try {
FileInputStream file = new FileInputStream(Environment.getExternalStorageDirectory().getPath() + "/aaaaa/baidu_map.apk");
fileSize = file.available();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(uploadThread).start();
}
private Thread uploadThread = new Thread(){
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL("http://192.168.23.1:8080/TestProject/MoreUploadTest");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setChunkedStreamingMode(51200);
connection.setUseCaches(false);
// 設定允許輸出
connection.setDoOutput(true);
// 設定斷點開始,結束位置
connection.setRequestProperty("Range", "bytes=" + start + "-" + stop);
String path = Environment.getExternalStorageDirectory().getPath() + "/aaaaa/baidu_map.apk";
RandomAccessFile file = new RandomAccessFile(path, "rw");
file.seek(start);
byte[] buffer = new byte[1024];
int count = 0;
OutputStream os = connection.getOutputStream();
if(fileSize > 1024*1024){
for(int i=0; i<1024 && count!=-1; i++){
count = file.read(buffer);
os.write(buffer, 0, count);
}
}else{
for(int i=0; i<(fileSize/1024)+1 && count!=-1; i++){
count = file.read(buffer);
os.write(buffer, 0, count);
}
}
os.flush();
os.close();
Log.e("ABC", connection.getResponseCode() + "");
if(connection.getResponseCode() == 200){
result += StringStreamUtil.inputStreamToString(connection.getInputStream()) + "\n";
}
start = stop + 1;
stop += 1024*1024;
fileSize -= 1024*1024;
Message msg = Message.obtain();
msg.what = 0;
uploadHandler.sendMessage(msg);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(connection != null){
connection.disconnect();
}
}
};
};
private Handler uploadHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
if(msg.what == 0){
if(times >= 8){
mTvMsg.setText(result);
}else{
times += 1;
new Thread(uploadThread).start();
mTvMsg.setText(result);
}
}
};
};
}
六、檔案斷點下載:
1、下載重要的是實現技巧: 一:設定斷點請求setRequestProperty("Range", "bytes=0-1024");
二:通過RandomAccessFile來將下載的位元組插入到指定的位置。
2、對於輸出流的三個方法的對比:
os.write(byte[] buffer); 可能錯誤,因為你每次讀取的資料小於等於1024,但你每次寫入的資料仍然是1024, 對圖片有一定影響,對安裝包絕對是致命的影響。
os.write(int oneByte); 效率低
os.write(byte[] buffer, int byteOffset, int byteCount); 效率高,和第二個方法相比有一個數量級的差別(主觀上看,有興趣的可以測幾下)。
/**
* 斷點下載
*/
public class MoreTimesActivity extends Activity {
private TextView mTvMsg;
private String result = "";
private long start = 0;
private long stop = 1024 * 1024;
private int times = 0; // 根據檔案大小自己設的,
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_times_download);
initView();
}
private void initView(){
mTvMsg = (TextView) findViewById(R.id.tv_msg);
new Thread(moreThread).start();
}
private Thread moreThread = new Thread(){
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL("http://ftp-apk.pconline.com.cn/ef19af4e28462271af1117efaf868bc2/pub/download/201010/renshengrili_v4.0.04.05.apk");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setDoInput(true);
// 設定開始下載的位置和結束下載的位置,單位為位元組
connection.setRequestProperty("Range", "bytes=" + start + "-" + stop);
String path = Environment.getExternalStorageDirectory().getPath() + "/aaaaa/baidu_map.apk";
// 斷點下載使用的檔案物件RandomAccessFile
RandomAccessFile access = new RandomAccessFile(path, "rw");
// 移動指標到開始位置
access.seek(start);
InputStream is = null;
Log.e("ADB----", connection.getResponseCode() + "");
if(connection.getResponseCode() == 206){
is = connection.getInputStream();
int count = 0;
byte[] buffer = new byte[1024];
while((count = is.read(buffer)) != -1){
access.write(buffer, 0, count);
}
}
if(access != null){
access.close();
}
if(is != null){
is.close();
}
start = stop + 1;
stop += 1024*1024; // 每次下載1M
Message msg = Message.obtain();
msg.what = 0;
result += "檔案" + times + "下載成功" + ":" + start + "---" + stop + "\n";
moreHandler.sendMessage(msg);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(connection != null){
connection.disconnect();
}
}
};
};
private Handler moreHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
if(msg.what == 0 && result!=null){
if(times >= 10){
Message msg1 = Message.obtain();
msg1.what = 1;
moreHandler.sendMessage(msg1);
}else{
new Thread(moreThread).start();
times += 1;
}
mTvMsg.setText(result);
}else if(msg.what == 1){
mTvMsg.setText(result);
}
};
};
}
三、Demo講解
為了演示HttpURLConnection的常見用法,我做了一個App,介面如下所示:
主介面MainActivity有四個按鈕,分別表示用GET傳送請求、用POST傳送鍵值對資料、用POST傳送XML資料以及用POST傳送JSON資料,點選對應的按鈕會啟動NetworkActivity並執行相應的操作。
NetworkActivity的原始碼如下所示,此處先貼出程式碼,後面會詳細說明。
package com.ispring.httpurlconnection;
import android.content.Intent;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class NetworkActivity extends AppCompatActivity {
private NetworkAsyncTask networkAsyncTask = new NetworkAsyncTask();
private TextView tvUrl = null;
private TextView tvRequestHeader = null;
private TextView tvRequestBody = null;
private TextView tvResponseHeader = null;
private TextView tvResponseBody = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network);
tvUrl = (TextView) findViewById(R.id.tvUrl);
tvRequestHeader = (TextView) findViewById(R.id.tvRequestHeader);
tvRequestBody = (TextView) findViewById(R.id.tvRequestBody);
tvResponseHeader = (TextView) findViewById(R.id.tvResponseHeader);
tvResponseBody = (TextView) findViewById(R.id.tvResponseBody);
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
String networkAction = intent.getStringExtra("action");
networkAsyncTask.execute(networkAction);
}
}
//用於進行網路請求的AsyncTask
class NetworkAsyncTask extends AsyncTask<String, Integer, Map<String, Object>> {
//NETWORK_GET表示傳送GET請求
public static final String NETWORK_GET = "NETWORK_GET";
//NETWORK_POST_KEY_VALUE表示用POST傳送鍵值對資料
public static final String NETWORK_POST_KEY_VALUE = "NETWORK_POST_KEY_VALUE";
//NETWORK_POST_XML表示用POST傳送XML資料
public static final String NETWORK_POST_XML = "NETWORK_POST_XML";
//NETWORK_POST_JSON表示用POST傳送JSON資料
public static final String NETWORK_POST_JSON = "NETWORK_POST_JSON";
@Override
protected Map<String, Object> doInBackground(String... params) {
Map<String,Object> result = new HashMap<>();
URL url = null;//請求的URL地址
HttpURLConnection conn = null;
String requestHeader = null;//請求頭
byte[] requestBody = null;//請求體
String responseHeader = null;//響應頭
byte[] responseBody = null;//響應體
String action = params[0];//http請求的操作型別
try {
if (NETWORK_GET.equals(action)) {
//傳送GET請求
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet?name=孫群&age=27");
conn = (HttpURLConnection) url.openConnection();
//HttpURLConnection預設就是用GET傳送請求,所以下面的setRequestMethod可以省略
conn.setRequestMethod("GET");
//HttpURLConnection預設也支援從服務端讀取結果流,所以下面的setDoInput也可以省略
conn.setDoInput(true);
//用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
conn.setRequestProperty("action", NETWORK_GET);
//禁用網路快取
conn.setUseCaches(false);
//獲取請求頭
requestHeader = getReqeustHeader(conn);
//在對各種引數配置完成後,通過呼叫connect方法建立TCP連線,但是並未真正獲取資料
//conn.connect()方法不必顯式呼叫,當呼叫conn.getInputStream()方法時內部也會自動呼叫connect方法
conn.connect();
//呼叫getInputStream方法後,服務端才會收到請求,並阻塞式地接收服務端返回的資料
InputStream is = conn.getInputStream();
//將InputStream轉換成byte陣列,getBytesByInputStream會關閉輸入流
responseBody = getBytesByInputStream(is);
//獲取響應頭
responseHeader = getResponseHeader(conn);
} else if (NETWORK_POST_KEY_VALUE.equals(action)) {
//用POST傳送鍵值對資料
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
//通過setRequestMethod將conn設定成POST方法
conn.setRequestMethod("POST");
//呼叫conn.setDoOutput()方法以顯式開啟請求體
conn.setDoOutput(true);
//用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
//獲取請求頭
requestHeader = getReqeustHeader(conn);
//獲取conn的輸出流
OutputStream os = conn.getOutputStream();
//獲取兩個鍵值對name=孫群和age=27的位元組陣列,將該位元組陣列作為請求體
requestBody = new String("name=孫群&age=27").getBytes("UTF-8");
//將請求體寫入到conn的輸出流中
os.write(requestBody);
//記得呼叫輸出流的flush方法
os.flush();
//關閉輸出流
os.close();
//當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
InputStream is = conn.getInputStream();
//獲得響應體的位元組陣列
responseBody = getBytesByInputStream(is);
//獲得響應頭
responseHeader = getResponseHeader(conn);
} else if (NETWORK_POST_XML.equals(action)) {
//用POST傳送XML資料
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
//通過setRequestMethod將conn設定成POST方法
conn.setRequestMethod("POST");
//呼叫conn.setDoOutput()方法以顯式開啟請求體
conn.setDoOutput(true);
//用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
conn.setRequestProperty("action", NETWORK_POST_XML);
//獲取請求頭
requestHeader = getReqeustHeader(conn);
//獲取conn的輸出流
OutputStream os = conn.getOutputStream();
//讀取assets目錄下的person.xml檔案,將其位元組陣列作為請求體
requestBody = getBytesFromAssets("person.xml");
//將請求體寫入到conn的輸出流中
os.write(requestBody);
//記得呼叫輸出流的flush方法
os.flush();
//關閉輸出流
os.close();
//當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
InputStream is = conn.getInputStream();
//獲得響應體的位元組陣列
responseBody = getBytesByInputStream(is);
//獲得響應頭
responseHeader = getResponseHeader(conn);
} else if (NETWORK_POST_JSON.equals(action)) {
//用POST傳送JSON資料
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
//通過setRequestMethod將conn設定成POST方法
conn.setRequestMethod("POST");
//呼叫conn.setDoOutput()方法以顯式開啟請求體
conn.setDoOutput(true);
//用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
conn.setRequestProperty("action", NETWORK_POST_JSON);
//獲取請求頭
requestHeader = getReqeustHeader(conn);
//獲取conn的輸出流
OutputStream os = conn.getOutputStream();
//讀取assets目錄下的person.json檔案,將其位元組陣列作為請求體
requestBody = getBytesFromAssets("person.json");
//將請求體寫入到conn的輸出流中
os.write(requestBody);
//記得呼叫輸出流的flush方法
os.flush();
//關閉輸出流
os.close();
//當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
InputStream is = conn.getInputStream();
//獲得響應體的位元組陣列
responseBody = getBytesByInputStream(is);
//獲得響應頭
responseHeader = getResponseHeader(conn);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//最後將conn斷開連線
if (conn != null) {
conn.disconnect();
}
}
result.put("url", url.toString());
result.put("action", action);
result.put("requestHeader", requestHeader);
result.put("requestBody", requestBody);
result.put("responseHeader", responseHeader);
result.put("responseBody", responseBody);
return result;
}
@Override
protected void onPostExecute(Map<String, Object> result) {
super.onPostExecute(result);
String url = (String)result.get("url");//請求的URL地址
String action = (String) result.get("action");//http請求的操作型別
String requestHeader = (String) result.get("requestHeader");//請求頭
byte[] requestBody = (byte[]) result.get("requestBody");//請求體
String responseHeader = (String) result.get("responseHeader");//響應頭
byte[] responseBody = (byte[]) result.get("responseBody");//響應體
//更新tvUrl,顯示Url
tvUrl.setText(url);
//更新tvRequestHeader,顯示請求頭
if (requestHeader != null) {
tvRequestHeader.setText(requestHeader);
}
//更新tvRequestBody,顯示請求體
if(requestBody != null){
try{
String request = new String(requestBody, "UTF-8");
tvRequestBody.setText(request);
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
}
//更新tvResponseHeader,顯示響應頭
if (responseHeader != null) {
tvResponseHeader.setText(responseHeader);
}
//更新tvResponseBody,顯示響應體
if (NETWORK_GET.equals(action)) {
String response = getStringByBytes(responseBody);
tvResponseBody.setText(response);
} else if (NETWORK_POST_KEY_VALUE.equals(action)) {
String response = getStringByBytes(responseBody);
tvResponseBody.setText(response);
} else if (NETWORK_POST_XML.equals(action)) {
//將表示xml的位元組陣列進行解析
String response = parseXmlResultByBytes(responseBody);
tvResponseBody.setText(response);
} else if (NETWORK_POST_JSON.equals(action)) {
//將表示json的位元組陣列進行解析
String response = parseJsonResultByBytes(responseBody);
tvResponseBody.setText(response);
}
}
//讀取請求頭
private String getReqeustHeader(HttpURLConnection conn) {
//https://github.com/square/okhttp/blob/master/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/HttpURLConnectionImpl.java#L236
Map<String, List<String>> requestHeaderMap = conn.getRequestProperties();
Iterator<String> requestHeaderIterator = requestHeaderMap.keySet().iterator();
StringBuilder sbRequestHeader = new StringBuilder();
while (requestHeaderIterator.hasNext()) {
String requestHeaderKey = requestHeaderIterator.next();
String requestHeaderValue = conn.getRequestProperty(requestHeaderKey);
sbRequestHeader.append(requestHeaderKey);
sbRequestHeader.append(":");
sbRequestHeader.append(requestHeaderValue);
sbRequestHeader.append("\n");
}
return sbRequestHeader.toString();
}
//讀取響應頭
private String getResponseHeader(HttpURLConnection conn) {
Map<String, List<String>> responseHeaderMap = conn.getHeaderFields();
int size = responseHeaderMap.size();
StringBuilder sbResponseHeader = new StringBuilder();
for(int i = 0; i < size; i++){
String responseHeaderKey = conn.getHeaderFieldKey(i);
String responseHeaderValue = conn.getHeaderField(i);
sbResponseHeader.append(responseHeaderKey);
sbResponseHeader.append(":");
sbResponseHeader.append(responseHeaderValue);
sbResponseHeader.append("\n");
}
return sbResponseHeader.toString();
}
//根據位元組陣列構建UTF-8字串
private String getStringByBytes(byte[] bytes) {
String str = "";
try {
str = new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return str;
}
//從InputStream中讀取資料,轉換成byte陣列,最後關閉InputStream
private byte[] getBytesByInputStream(InputStream is) {
byte[] bytes = null;
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(baos);
byte[] buffer = new byte[1024 * 8];
int length = 0;
try {
while ((length = bis.read(buffer)) > 0) {
bos.write(buffer, 0, length);
}
bos.flush();
bytes = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bytes;
}
//根據檔名,從asserts目錄中讀取檔案的位元組陣列
private byte[] getBytesFromAssets(String fileName){
byte[] bytes = null;
AssetManager assetManager = getAssets();
InputStream is = null;
try{
is = assetManager.open(fileName);
bytes = getBytesByInputStream(is);
}catch (IOException e){
e.printStackTrace();
}
return bytes;
}
//將表示xml的位元組陣列進行解析
private String parseXmlResultByBytes(byte[] bytes) {
InputStream is = new ByteArrayInputStream(bytes);
StringBuilder sb = new StringBuilder();
List<Person> persons = XmlParser.parse(is);
for (Person person : persons) {
sb.append(person.toString()).append("\n");
}
return sb.toString();
}
//將表示json的位元組陣列進行解析
private String parseJsonResultByBytes(byte[] bytes){
String jsonString = getStringByBytes(bytes);
List<Person> persons = JsonParser.parse(jsonString);
StringBuilder sb = new StringBuilder();
for (Person person : persons) {
sb.append(person.toString()).append("\n");
}
return sb.toString();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
這個App是用來傳送http請求的客戶端,除此之外,我還建立了一個JSP的WebProject作為服務端,用Servlet對客戶端發來的請求進行處理,Servlet的程式碼如下所示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Enumeration;
@WebServlet(name = "MyServlet")
public class MyServlet extends HttpServlet {
//GET請求
private static final String NETWORK_GET = "NETWORK_GET";
//用POST傳送鍵值對
private static final String NETWORK_POST_KEY_VALUE = "NETWORK_POST_KEY_VALUE";
//用POST傳送XML資料
private static final String NETWORK_POST_XML = "NETWORK_POST_XML";
//用POST傳送JSON資料
private static final String NETWORK_POST_JSON = "NETWORK_POST_JSON";
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getHeader("action");
//將輸入與輸出都設定為UTF-8編碼
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/plain;charset=UTF-8");
//response.setHeader("content-type","text/plain;charset=UTF-8");
if(NETWORK_GET.equals(action) || NETWORK_POST_KEY_VALUE.equals(action)){
//對於NETWORK_GET和NETWORK_POST_KEY_VALUE,遍歷鍵值對,並將鍵值對重新寫回到輸出結果中
Enumeration<String> parameterNames = request.getParameterNames();
PrintWriter writer = response.getWriter();
while(parameterNames.hasMoreElements()){
String name = parameterNames.nextElement();
String value = request.getParameter(name);
if(request.getMethod().toUpperCase().equals("GET")){
//GET請求需要進行編碼轉換,POST不需要
value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
}
writer.write(name + "=" + value + "\n");
}
writer.flush();
writer.close();
}else if(NETWORK_POST_XML.equals(action) || NETWORK_POST_JSON.equals(action)){
//對於NETWORK_POST_XML和NETWORK_POST_JSON,將請求體重新寫入到響應體的輸出流中
//通過request.getInputStream()得到http請求的請求體
BufferedInputStream bis = new BufferedInputStream(request.getInputStream());
//通過response.getOutputStream()得到http請求的響應體
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
byte[] buffer = new byte[1024 * 8];
int length = 0;
while ( (length = bis.read(buffer)) > 0){
bos.write(buffer, 0, length);
}
bos.flush();
bos.close();
bis.close();
}else{
PrintWriter writer = response.getWriter();
writer.write("非法的請求頭: action");
writer.flush();
writer.close();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
傳送GET請求
由於網路請求耗時而且會阻塞當前執行緒,所以我們將傳送http請求的操作都放到NetworkAsyncTask中,NetworkAsyncTask是繼承自AsyncTask。
點選”GET”按鈕後,介面如下所示:
GET請求是最簡單的http請求,其傳送請求的程式碼如下所示:
if (NETWORK_GET.equals(action)) {
//傳送GET請求
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet?name=孫群&age=27");
conn = (HttpURLConnection) url.openConnection();
//HttpURLConnection預設就是用GET傳送請求,所以下面的setRequestMethod可以省略
conn.setRequestMethod("GET");
//HttpURLConnection預設也支援從服務端讀取結果流,所以下面的setDoInput也可以省略
conn.setDoInput(true);
//用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
conn.setRequestProperty("action", NETWORK_GET);
//禁用網路快取
conn.setUseCaches(false);
//獲取請求頭
requestHeader = getReqeustHeader(conn);
//在對各種引數配置完成後,通過呼叫connect方法建立TCP連線,但是並未真正獲取資料
//conn.connect()方法不必顯式呼叫,當呼叫conn.getInputStream()方法時內部也會自動呼叫connect方法
conn.connect();
//呼叫getInputStream方法後,服務端才會收到請求,並阻塞式地接收服務端返回的資料
InputStream is = conn.getInputStream();
//將InputStream轉換成byte陣列,getBytesByInputStream會關閉輸入流
responseBody = getBytesByInputStream(is);
//獲取響應頭
responseHeader = getResponseHeader(conn);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
上面的註釋寫的比較詳細了,此處對程式碼進行一下簡單說明。
我們在URL的後面新增了
?name=孫群&age=27
,這樣就相當於新增了兩個鍵值對,其形式如?key1=value1&key2=value2&key3=value3
,在?
後面新增鍵值對,鍵和值之間用=
相連,鍵值對之間用&
分隔,服務端可以讀取這些鍵值對資訊。HttpURLConnection預設就是用GET傳送請求,當然也可以用conn.setRequestMethod(“GET”)將其顯式地設定為GET請求。
通過setRequestProperty方法可以設定請求頭,既可以是標準的請求頭,也可以是自定義的請求頭,此處我們設定了自定義的請求頭action,用於服務端判斷請求的型別。
GET請求容易被快取,我們可以用conn.setUseCaches(false)禁用快取。
通過呼叫connect方法可以讓客戶端和伺服器之間建立TCP連線,建立連線之後不會立即傳輸資料,只是表示處於connected狀態了。該方法不必顯式呼叫,因為在之後的getInputStream方法中會隱式地呼叫該方法。
呼叫HttpURLConnection的getInputStream()方法可以獲得響應結果的輸入流,即伺服器向客戶端輸出的資訊,需要注意的是,getInputStream()方法的呼叫必須在一系列的set方法之後進行。
然後在方法getBytesByInputStream中,通過輸入流的read方法得到位元組陣列,read方法是阻塞式的,每read一次,其實就是從伺服器上下載一部分資料,直到將伺服器的輸出全部下載完成,這樣就得到響應體responseBody了。
我們可以通過getReqeustHeader方法讀取請求頭,程式碼如下所示:
//讀取請求頭 private String getReqeustHeader(HttpURLConnection conn) { Map<String, List<String>> requestHeaderMap = conn.getRequestProperties(); Iterator<String> requestHeaderIterator = requestHeaderMap.keySet().iterator(); StringBuilder sbRequestHeader = new StringBuilder(); while (requestHeaderIterator.hasNext()) { String requestHeaderKey = requestHeaderIterator.next(); String requestHeaderValue = conn.getRequestProperty(requestHeaderKey); sbRequestHeader.append(requestHeaderKey); sbRequestHeader.append(":"); sbRequestHeader.append(requestHeaderValue); sbRequestHeader.append("\n"); } return sbRequestHeader.toString(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
由上可以看出,以上方法主要還是呼叫了HttpURLConnection的方法getRequestProperty獲取請求頭,需要注意的是getRequestProperty方法執行時,客戶端和伺服器之間必須還未建立TCP連線,即還沒有呼叫connect方法,在connected之後執行getRequestProperty會丟擲異常,詳見https://github.com/square/okhttp/blob/master/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/HttpURLConnectionImpl.java#L236
我們還可以通過getResponseHeader方法獲取響應頭,程式碼如下所示:
//讀取響應頭 private String getResponseHeader(HttpURLConnection conn) { Map<String, List<String>> responseHeaderMap = conn.getHeaderFields(); int size = responseHeaderMap.size(); StringBuilder sbResponseHeader = new StringBuilder(); for(int i = 0; i < size; i++){ String responseHeaderKey = conn.getHeaderFieldKey(i); String responseHeaderValue = conn.getHeaderField(i); sbResponseHeader.append(responseHeaderKey); sbResponseHeader.append(":"); sbResponseHeader.append(responseHeaderValue); sbResponseHeader.append("\n"); } return sbResponseHeader.toString(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
通過方法getHeaderFieldKey可以獲得響應頭的key值,通過方法getHeaderField可以獲得響應頭的value值。
在後臺的Servelt中將鍵值對資訊重新原樣寫入到客戶端,服務端的程式碼如下所示:
if(NETWORK_GET.equals(action) || NETWORK_POST_KEY_VALUE.equals(action)){
//對於NETWORK_GET和NETWORK_POST_KEY_VALUE,遍歷鍵值對,並將鍵值對重新寫回到輸出結果中
Enumeration<String> parameterNames = request.getParameterNames();
PrintWriter writer = response.getWriter();
while(parameterNames.hasMoreElements()){
String name = parameterNames.nextElement();
String value = request.getParameter(name);
if(request.getMethod().toUpperCase().equals("GET")){
//GET請求需要進行編碼轉換,POST不需要
value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
}
writer.write(name + "=" + value + "\n");
}
writer.flush();
writer.close();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
在接收到服務端返回的資料後,在AsyncTask的onPostExecute方法中會將Url、請求頭、響應頭、響應體的值展示在UI上。
用POST傳送鍵值對資料
點選”POST KEY VALUE”按鈕,可以用POST傳送鍵值對資料,介面如下所示:
程式碼如下所示:
if (NETWORK_POST_KEY_VALUE.equals(action)) {
//用POST傳送鍵值對資料
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
//通過setRequestMethod將conn設定成POST方法
conn.setRequestMethod("POST");
//呼叫conn.setDoOutput()方法以顯式開啟請求體
conn.setDoOutput(true);
//用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
//獲取請求頭
requestHeader = getReqeustHeader(conn);
//獲取conn的輸出流
OutputStream os = conn.getOutputStream();
//獲取兩個鍵值對name=孫群和age=27的位元組陣列,將該位元組陣列作為請求體
requestBody = new String("name=孫群&age=27").getBytes("UTF-8");
//將請求體寫入到conn的輸出流中
os.write(requestBody);
//記得呼叫輸出流的flush方法
os.flush();
//關閉輸出流
os.close();
//當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
InputStream is = conn.getInputStream();
//獲得響應體的位元組陣列
responseBody = getBytesByInputStream(is);
//獲得響應頭
responseHeader = getResponseHeader(conn);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
使用POST傳送請求的程式碼與用GET傳送請求的程式碼大部分類似,我們只對其中不同的地方做下說明。
需要通過setRequestMethod將conn設定成POST方法。
如果想用POST傳送請求體,那麼需要呼叫setDoOutput方法,將其設定為true。
通過conn.getOutputStream()獲得輸出流,可以向輸出流中寫入請求體,最後記得呼叫輸出流的flush方法,注意此時並沒有真正將請求體傳送到伺服器端。
當呼叫getInputStream方法後,才真正將請求體的內容傳送到伺服器。
在我們的伺服器端的Servlet中,在接收到POST請求傳送的鍵值對資料後,也只是簡單地將鍵值對資料原樣寫入給客戶端,具體程式碼參見上文,不再贅述。
用POST傳送XML資料
點選”POST XML”按鈕,可以用POST傳送XML資料,介面如下所示:
程式碼如下所示:
if (NETWORK_POST_XML.equals(action)) {
//用POST傳送XML資料
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
//通過setRequestMethod將conn設定成POST方法
conn.setRequestMethod("POST");
//呼叫conn.setDoOutput()方法以顯式開啟請求體
conn.setDoOutput(true);
//用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
conn.setRequestProperty("action", NETWORK_POST_XML);
//獲取請求頭
requestHeader = getReqeustHeader(conn);
//獲取conn的輸出流
OutputStream os = conn.getOutputStream();
//讀取assets目錄下的person.xml檔案,將其位元組陣列作為請求體
requestBody = getBytesFromAssets("person.xml");
//將請求體寫入到conn的輸出流中
os.write(requestBody);
//記得呼叫輸出流的flush方法
os.flush();
//關閉輸出流
os.close();
//當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
InputStream is = conn.getInputStream();
//獲得響應體的位元組陣列
responseBody = getBytesByInputStream(is);
//獲得響應頭
responseHeader = getResponseHeader(conn);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
上面的程式碼與用POST傳送鍵值對的程式碼很相似,對其進行簡單說明。
上述程式碼通過getBytesFromAssets方法讀取了assets目錄下的person.xml檔案,將xml檔案的位元組流作為請求體requestBody,然後將該請求體傳送到伺服器。
person.xml檔案如下所示:
<?xml version="1.0" encoding="utf-8"?> <persons> <person id="101"> <name>張三</name> <age>27</age> </person> <person id="102"> <name>李四</name> <age>28</age> </person> </persons>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
<person>
標籤對應著Person
類,Person
類程式碼如下所示:package com.ispring.httpurlconnection; public class Person { private String id = ""; private String name = ""; private int age = 0; public String getId(){ return id; } public void setId(String id){ this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return new StringBuilder().append("name:").append(getName()).append(", age:").append(getAge()).toString(); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
服務端接收到客戶端傳送來的XML資料之後,只是簡單的將其原樣寫回到客戶端,即客戶端接收到的響應體還是原來的XML資料,然後客戶端通過parseXmlResultByBytes方法對該XML資料進行解析,將位元組陣列轉換成
List<Person>
,parseXmlResultByBytes程式碼如下所示://將表示xml的位元組陣列進行解析 private String parseXmlResultByBytes(byte[] bytes) { InputStream is = new ByteArrayInputStream(bytes); StringBuilder sb = new StringBuilder(); List<Person> persons = XmlParser.parse(is); for (Person person : persons) { sb.append(person.toString()).append("\n"); } return sb.toString(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
該方法使用了自定義的XmlParser類對XML資料進行解析,其原始碼如下所示:
package com.ispring.httpurlconnection; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; public class XmlParser { public static List<Person> parse(InputStream is) { List<Person> persons = new ArrayList<>(); try{ SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); PersonHandler personHandler = new PersonHandler(); parser.parse(is, personHandler); persons = personHandler.getPersons(); }catch (Exception e){ e.printStackTrace(); } return persons; } static class PersonHandler extends DefaultHandler { private List<Person> persons; private Person temp; private StringBuilder sb; public List<Person> getPersons(){ return persons; } @Override public void startDocument() throws SAXException { super.startDocument(); persons = new ArrayList<>(); sb = new StringBuilder(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); sb.setLength(0); if(localName.equals("person")){ temp = new Person(); int length = attributes.getLength(); for(int i = 0; i < length; i++){ String name = attributes.getLocalName(i); if(name.equals("id")){ String value = attributes.getValue(i); temp.setId(value); } } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); sb.append(ch, start, length); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); if(localName.equals("name")){ String name = sb.toString(); temp.setName(name); }else if(localName.equals("age")){ int age = Integer.parseInt(sb.toString()); temp.setAge(age); }else if(localName.equals("person")){ persons.add(temp); } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
用POST傳送JSON資料
點選”POST JSON”按鈕,可以用POST傳送JSON資料,介面如下所示:
程式碼如下所示:
if (NETWORK_POST_JSON.equals(action)) {
//用POST傳送JSON資料
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
//通過setRequestMethod將conn設定成POST方法
conn.setRequestMethod("POST");
//呼叫conn.setDoOutput()方法以顯式開啟請求體
conn.setDoOutput(true);
//用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
conn.setRequestProperty("action", NETWORK_POST_JSON);
//獲取請求頭
requestHeader = getReqeustHeader(conn);
//獲取conn的輸出流
OutputStream os = conn.getOutputStream();
//讀取assets目錄下的person.json檔案,將其位元組陣列作為請求體
requestBody = getBytesFromAssets("person.json");
//將請求體寫入到conn的輸出流中
os.write(requestBody);
//記得呼叫輸出流的flush方法
os.flush();
//關閉輸出流
os.close();
//當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
InputStream is = conn.getInputStream();
//獲得響應體的位元組陣列
responseBody = getBytesByInputStream(is);
//獲得響應頭
responseHeader = getResponseHeader(conn);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
上面的程式碼與用POST傳送XML的程式碼很相似,對其進行簡單說明。
- 上述程式碼通過getBytesFromAssets方法讀取了assets目錄下的person.json檔案,將json檔案的位元組流作為請求體requestBody,然後將該請求體傳送到伺服器。
person.json檔案如下所示:
{ "persons": [{ "id": "101", "name":"張三", "age":27 }, { "id": "102", "name":"李四", "age":28 }] }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
persons陣列中的每個元素都對應著一個
Person
物件。服務端接收到客戶端傳送來的JSON資料之後,只是簡單的將其原樣寫回到客戶端,即客戶端接收到的響應體還是原來的JSON資料,然後客戶端通過parseJsonResultByBytes方法對該XML資料進行解析,將位元組陣列轉換成List,parseXmlResultByBytes程式碼如下所示:
//將表示json的位元組陣列進行解析 private String parseJsonResultByBytes(byte[] bytes){ String jsonString = getStringByBytes(bytes); List<Person> persons = JsonParser.parse(jsonString); StringBuilder sb = new StringBuilder(); for (Person person : persons) { sb.append(person.toString()).append("\n"); } return sb.toString(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
該方法又使用了自定義的JsonParset類,原始碼如下所示:
package com.ispring.httpurlconnection; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; public class JsonParser { public static List<Person> parse(String jsonString){ List<Person> persons = new ArrayList<>(); try{ JSONObject jsonObject = new JSONObject(jsonString); JSONArray jsonArray = jsonObject.getJSONArray("persons"); int length = jsonArray.length(); for(int i = 0; i < length; i++){ JSONObject personObject = jsonArray.getJSONObject(i); String id = personObject.getString("id"); String name = personObject.getString("name"); int age = personObject.getInt("age"); Person person = new Person(); person.setId(id); person.setName(name); person.setAge(age); persons.add(person); } }catch (JSONException e){ e.printStackTrace(); } return persons; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
其他
如果Http請求體的資料很大,就可以認為該請求主要是完成資料上傳的作用;如果響應體的資料很大,就可以認為該請求主要完成資料下載的作用。
上面我們通過demo演示瞭如何上傳XML檔案和JSON檔案,並對二者進行解析。在上傳的過程中,Android要寫入Content-Length這個請求頭,Content-Length就是請求體的位元組長度,注意是位元組長度,而不是字元長度(漢字等會佔用兩個位元組)。預設情況下,Android為了得到Content-Length的長度,Android會把請求體放到記憶體中的,直到輸出流呼叫了close方法後,才會讀取記憶體中請求體的位元組長度,將其作為請求頭Content-Length。當要上傳的請求體很大時,這會非常佔用記憶體,為此Android提供了兩個方法來解決這個問題。
setFixedLengthStreamingMode (int contentLength)
如果請求體的大小是知道的,那麼可以呼叫HttpURLConnection的setFixedLengthStreamingMode (int contentLength) 方法,該方法會告訴Android要傳輸的請求頭Content-Length的大小,這樣Android就無需讀取整個請求體的大小,從而不必一下將請求體全部放到記憶體中,這樣就避免了請求體佔用巨大記憶體的問題。setChunkedStreamingMode (int chunkLength)
如果請求體的大小不知道,那麼可以呼叫setChunkedStreamingMode (int chunkLength)方法。該方法將傳輸的請求體分塊傳輸,即將原始的資料分成多個資料塊,chunkLength表示每塊傳輸的位元組大小。比如我們要傳輸的請求體大小是10M,我們將chunkLength設定為1024 * 1024 byte,即1M,那麼Android會將請求體分10次傳輸,每次傳輸1M,具體的傳輸規則是:每次傳輸一個資料塊時,首先在一行中寫明該資料塊的長度,比如1024 * 1024,然後在後面的一行中寫入要傳輸的資料塊的位元組陣列,再然後是一個空白行,這樣第一資料塊就這樣傳輸,在空白行之後就是第二個資料塊的傳輸,與第一個資料塊的格式一樣,直到最後沒有資料塊要傳輸了,就在用一行寫明要傳輸的位元組為0,這樣在伺服器端就知道讀取完了整個請求體了。如果設定的chunkLength的值為0,那麼表示Android會使用預設的一個值作為實際的chunkLength。
使用setChunkedStreamingMode方法的前提是伺服器支援分塊資料傳輸,分塊資料傳輸是從HTTP 1.1開始支援的,所以如果你的伺服器只支援HTTP 1.0的話,那麼不能使用setChunkedStreamingMode方法。
希望本文對大家使用HttpURLConnection有所幫助!
相關文章
- Android HTTP協議請求網路(一)之認識探索AndroidHTTP協議
- Android探索之HttpURLConnection網路請求AndroidHTTP
- CTFHub web前置技能HTTP協議請求方式WebHTTP協議
- 安卓開發之網路請求HttpURLConnection安卓HTTP
- Swift:面向協議的網路請求Swift協議
- HTTP 協議六種請求方法HTTP協議
- HTTP協議如何發起請求HTTP協議
- http協議請求方法有哪些?網路安全技術入門HTTP協議
- Android Http請求框架二:xUtils 框架網路請求AndroidHTTP框架
- Android測試Http網路請求。AndroidHTTP
- Python 之requests封裝通用http協議介面請求Python封裝HTTP協議
- Android使用http協議訪問網路AndroidHTTP協議
- Android okHttp網路請求之Get/Post請求AndroidHTTP
- HTTP網路請求原理HTTP
- 協議層的攻擊:HTTP請求走私協議HTTP
- [譯]學習HTTP協議的請求行HTTP協議
- 計算機網路之十二:HTTP協議計算機網路HTTP協議
- HTTP協議的請求與資料抓包HTTP協議
- 解密協議層的攻擊——HTTP請求走私解密協議HTTP
- http協議學習-請求頭Request HeadersHTTP協議Header
- HTTP協議GET和POST請求的區別HTTP協議
- HTTP協議中請求方法的Get和PostHTTP協議
- Cocos2dx之http網路請求HTTP
- 網路通訊協議-HTTP協議詳解!協議HTTP
- 極光HTTP之什麼是網路協議HTTP協議
- Android網路請求(終) 網路請求框架RetrofitAndroid框架
- Android網路請求(3) 網路請求框架OkHttpAndroid框架HTTP
- HttpUrlConnection和HttpClient和android-async-http框架的GET和POST請求HTTPclientAndroid框架
- 網路基礎之HTTP協議HTTP協議
- Http協議什麼時候發生options請求?HTTP協議
- http協議學習系列(請求頭---Request Headers)HTTP協議Header
- Android網路請求(4) 網路請求框架VolleyAndroid框架
- Android Http請求AndroidHTTP
- Http網路協議包 (快速理解)HTTP協議
- 網路安全網路協議知識點中,http協議是什麼?協議HTTP
- 網路請求優化之取消請求優化
- HTTP協議---HTTP請求中的常用請求欄位和HTTP的響應狀態碼及響應頭HTTP協議
- Android中的幾種網路請求方式詳解Android