Android網路程式設計之一:原生網路訪問簡單封裝
前言:轉眼間2017了,去年給自己定的一週一篇部落格被拖成了一月一篇,後來忙著找工作也荒廢了。竟然還被CSDN部落格之星提名,又點燃了擼部落格的激情。
作為一個android codder,網略訪問基本上在專案中都會用到,常用的獲取JSON資料、下載檔案(圖片等)、上傳資料、上傳問檔案等等。Android原生的訪問網路有HttpClient
和HttpURLConnection
。在Android 6.0時刪除了HttpClient
,OkHttp
的呼聲也隨之高漲。
下面會對這幾種網路訪問模式寫下簡單的示例,網上大多示例都是訪問別人的伺服器或者本地的伺服器,不便於大家測試,我用阿里雲搭建了個簡單的服務端。
吐下槽,之前使用的MyEclipse
開發Java Web
,換電腦之後在網上搜怎麼配Mac
上Java Web
開發環境,有很多人提到使用IntelliJ IDEA
,搞環境又搞了一兩天,本人後端小菜~
NetworkTask編寫
這裡我們使用AsyncTask
的方式進行子執行緒訪問網路並回調到主執行緒,在建立NetworkTask
的時候要傳入是GET
還是POST
請求,然後傳入引數開始訪問網略獲取資料。
1. 這裡我們約定`GET`請求時引數即為拼接的請求地址,如: new xxxNetworkTask(NetworkTask.GET).execute("http://123.57.31.11/androidnet/getJoke?id=5"); 2. 約定`POST`請求時引數第一個為地址,其他為請求引數對,如: new xxxNetworkTask(NetworkTask.POST).execute("http://123.57.31.11/androidnet/getJoke", "id=5");
/**
* 訪問網路AsyncTask基類,訪問網路在子執行緒進行並返回主執行緒通知訪問的結果
*/
public abstract class NetworkTask extends AsyncTask<String, Integer, String> {
public static final String TAG = "NetworkTask";
public static String GET = "GET";
public static String POST = "POST";
private String mRequestMethod;
public NetworkTask(String method) {
this .mRequestMethod = method;
}
@Override
protected String doInBackground(String... params) {
String data;
if(GET.equals(mRequestMethod)) {
data = doGet(params[0]);
} else if(POST.equals(mRequestMethod)) {
data = doPost(params);
} else {
throw new RuntimeException("Request mode can only be GET or POST!");
}
return data;
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
}
/**
* 以GET的方式訪問網路
*
* @param url
* @return 返回的字串資料
*/
public abstract String doGet(String url);
/**
* 以POST的方式訪問網路
*
* @param params
* @return 返回的字串資料
*/
public abstract String doPost(String[] params);
}
然後我們加上返回資料的監聽回撥
/**
* 訪問網路AsyncTask基類,訪問網路在子執行緒進行並返回主執行緒通知訪問的結果
*/
public abstract class NetworkTask extends AsyncTask<String, Integer, String> {
public static final String TAG = "NetworkTask";
public static String GET = "GET";
public static String POST = "POST";
// 訪問方式,只能是GET或POST
private String mRequestMethod;
// 是否訪問網路成功
protected boolean isSuccess = true;
// 監聽回撥
private ResponceLintener mResponceLintener;
public NetworkTask(String method) {
this.mRequestMethod = method;
}
@Override
protected String doInBackground(String... params) {
String data;
String url = params[0];
if(GET.equals(mRequestMethod)) {
data = doGet(url);
} else if(POST.equals(mRequestMethod)) {
Map<String, String> paramMap = new HashMap<String, String>();
// 第一個引數為訪問的介面,不為body引數
for (int i = 1; i < params.length; i++) {
String[] value = new String[2];
if (params[i].split("=").length == 1) {
value[0] = params[i].split("=")[0];
value[1] = "";
} else {
value = params[i].split("=");
}
paramMap.put(value[0], value[1]);
}
data = doPost(url, paramMap);
} else {
throw new RuntimeException("Request mode can only be GET or POST!");
}
return data;
}
@Override
protected void onPostExecute(String result) {
if(null != mResponceLintener) {
if(isSuccess) {
mResponceLintener.onSuccess(result);
} else {
mResponceLintener.onError(result);
}
}
}
/**
* 以GET的方式訪問網路
*
* @param url
* @return 返回的字串資料
*/
public abstract String doGet(String url);
/**
* 以POST的方式訪問網路
*
* @param url
* @param paramMap
* @return 返回的字串資料
*/
public abstract String doPost(String url, Map<String, String> paramMap);
public void setResponceLintener(ResponceLintener l) {
this.mResponceLintener = l;
}
public interface ResponceLintener {
/**
* 成功的監聽回撥
* @param result
*/
void onSuccess(String result);
/**
* 失敗的監聽回撥
* @param error
*/
void onError(String error);
}
}
OK,經過簡單的封裝之後,我們在使用不同的形式訪問網路時只需要對應的去實現doGet
和doPost
就可以了。
HttpClient
儘管
HttpClient
刪除了,我們還是簡單回顧下它的使用。
HttpClientNetworkTask
建立HttpClientNetworkTask
繼承NetworkTask
並實現抽象方法。
/**
* 以HttpClient訪問網路AsyncTask,訪問網路在子執行緒進行並返回主執行緒通知訪問的結果
*/
public class HttpClientNetworkTask extends NetworkTask {
public HttpClientNetworkTask(String method) {
super(method);
}
@Override
public String doGet(String url) {
return null;
}
@Override
public String doPost(String url, Map<String, String> paramMap) {
return null;
}
}
下面我們只需要使用HttpClient
去完成響應的網路請求就可以啦~ 是不是很清爽~~
HttpClient使用步驟:
- 建立
HttpClient
物件; - 建立請求的物件,如果為
GET
請求,則建立HttpGet
物件,如果為POST
請求,則建立HttpPost
物件; - 呼叫
HttpClient
物件的execute
傳送請求,執行該方法後,將獲得伺服器返回的HttpResponse
物件; - 檢查相應狀態是否正常;
- 獲得相應物件當中的資料。
GET
@Override
public String doGet(String url) {
String result = null;
HttpClient httpCient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse httpResponse = httpCient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity entity = httpResponse.getEntity();
result = EntityUtils.toString(entity,"utf-8");
} else {
isSuccess = false;
result = "網路響應狀態碼不為200!";
}
} catch (IOException e) {
isSuccess = false;
result = "網路訪問錯誤:" + e.getMessage();
}
return result;
}
使用如下:
NetworkTask networkTask = new HttpClientNetworkTask(NetworkTask.GET);
networkTask.execute("http://123.57.31.11/androidnet/getJoke?id=5");
networkTask.setResponceLintener(new NetworkTask.ResponceLintener() {
@Override
public void onSuccess(String result) {
tv.setText(result);
}
@Override
public void onError(String error) {
Toast.makeText(getApplicationContext(), error, Toast.LENGTH_SHORT).show();
}
});
據說沒圖不便於理解,上圖:
POST
@Override
public String doPost(String url, Map<String, String> paramMap) {
String result;
HttpClient httpCient = new DefaultHttpClient();
HttpPost httpRequest = new HttpPost(url);
// 使用NameValuePair來儲存要傳遞的Post引數
List<NameValuePair> params = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : paramMap.entrySet()) {
params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
try {
httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
HttpResponse response = httpCient.execute(httpRequest);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
result = EntityUtils.toString(response.getEntity());
} else {
isSuccess = false;
result = "網路響應狀態碼不為200!";
}
} catch (IOException e) {
isSuccess = false;
result = "網路訪問錯誤:" + e.getMessage();
}
return result;
}
使用如下:
NetworkTask networkTask = new HttpClientNetworkTask(NetworkTask.POST);
networkTask.execute("http://123.57.31.11/androidnet/getJoke", "id=5");
networkTask.setResponceLintener(new NetworkTask.ResponceLintener() {
@Override
public void onSuccess(String result) {
tv.setText(result);
}
@Override
public void onError(String error) {
Toast.makeText(getApplicationContext(), error, Toast.LENGTH_SHORT).show();
}
});
HttpURLConnection
HttpURLConnectionNetworkTask
建立HttpURLConnectionNetworkTask
繼承NetworkTask
並實現抽象方法。
/**
* 以HttpURLConnection訪問網路AsyncTask,訪問網路在子執行緒進行並返回主執行緒通知訪問的結果
*/
public class HttpURLConnectionNetworkTask extends NetworkTask {
public HttpURLConnectionNetworkTask(String method) {
super(method);
}
@Override
public String doGet(String url) {
return null;
}
@Override
public String doPost(String url, Map<String, String> paramMap) {
return null;
}
}
下面我們用HttpURLConnection
去完成響應的網路。
HttpURLConnection使用步驟:
- 例項化URL物件;
- 例項化HttpUrlConnection物件;
- 設定請求連線屬性,傳遞引數等;
- 獲取返回碼判斷是否連結成功;
- 讀取輸入流;
- 關閉連結。
GET
@Override
public String doGet(String httpUrl) {
String result;
try {
URL url = new URL(httpUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(5000);
urlConnection.setConnectTimeout(5000);
urlConnection.setRequestProperty("Charset", "UTF-8");
if (urlConnection.getResponseCode() == 200) {
InputStream is = urlConnection.getInputStream();
result = readFromStream(is);
} else {
isSuccess = false;
result = "網路響應狀態碼不為200!";
}
} catch(IOException e) {
isSuccess = false;
result = "網路訪問錯誤:" + e.getMessage();
}
return result;
}
這裡編寫了一個從流獲取字串的工具
/**
* 輸入流獲取字串
*
* @param is 輸入流
* @return String 返回的字串
* @throws IOException
*/
public String readFromStream(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
is.close();
String result = baos.toString();
baos.close();
return result;
}
使用方法和HttpClientNetworkTask
一樣,只不過是把實現類由HttpClientNetworkTask
換為HttpURLConnection
:
NetworkTask networkTask = new HttpURLConnectionNetworkTask(NetworkTask.GET);
networkTask.execute("http://123.57.31.11/androidnet/getJoke?id=6");
networkTask.setResponceLintener(new NetworkTask.ResponceLintener() {
@Override
public void onSuccess(String result) {
tv.setText(result);
}
@Override
public void onError(String error) {
Toast.makeText(getApplicationContext(), error, Toast.LENGTH_SHORT).show();
}
});
POST
@Override
public String doPost(String httpUrl, Map<String, String> paramMap) {
String result;
try {
URL url = new URL(httpUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setReadTimeout(5000);
urlConnection.setConnectTimeout(5000);
// 配置連線的Content-type
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setDoOutput(true); // 傳送POST請求必須設定允許輸出
urlConnection.setDoInput(true); // 傳送POST請求必須設定允許輸入
String data = "";
boolean firstParam = true;
for (Map.Entry<String, String> entry : paramMap.entrySet()) {
if(firstParam) {
data += entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), "UTF-8");
firstParam = false;
} else {
data += "&" + entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), "UTF-8");
}
}
OutputStream os = urlConnection.getOutputStream();
os.write(data.getBytes());
os.flush();
if (urlConnection.getResponseCode() == 200) {
InputStream is = urlConnection.getInputStream();
result = readFromStream(is);
} else {
isSuccess = false;
result = "網路響應狀態碼不為200!";
}
} catch (IOException e) {
isSuccess = false;
result = "網路訪問錯誤:" + e.getMessage();
}
return result;
}
使用方法和HttpClientNetworkTask
一樣,只不過是把實現類由HttpClientNetworkTask
換為HttpURLConnection
:
NetworkTask networkTask = new HttpURLConnectionNetworkTask(NetworkTask.POST);
networkTask.execute("http://123.57.31.11/androidnet/getJoke", "id=6");
networkTask.setResponceLintener(new NetworkTask.ResponceLintener() {
@Override
public void onSuccess(String result) {
tv.setText(result);
}
@Override
public void onError(String error) {
Toast.makeText(getApplicationContext(), error, Toast.LENGTH_SHORT).show();
}
});
總結
其實開發的時候我們不是很關心具體是怎麼聯網的,封裝好之後使用起來越簡單越好。我給你地址和引數你去訪問網略然後返回我關心的資料就可以了。
以上是使用HttpClient
和HttpURLConnection
進行了GET
和POST
的返回資料為字串型別簡單封裝,有興趣的朋友也可以進行OkHttp
或其他方式的封裝。
當然在使用的時候可能會有更加複雜的場景,比如:上傳一個圖片、下載一個檔案等。
我們最常使用的還是獲取JSON型別的資料,然後封裝到實體。那麼這一塊如何封裝呢?接下來的一篇部落格將拋開NetworkTask
進行獲取JSON資料並封裝到實體的簡單編寫。