本篇文章主要介紹以幾下個(gè)知識(shí)點(diǎn):
- 使用 HTTP 協(xié)議訪問網(wǎng)絡(luò):
使用 HttpURLConnection 和 OKHttp亿扁;- 解析 XML 格式數(shù)據(jù):
Pull 和 SAX 解析;- 解析 JSON 數(shù)據(jù):
JSONObject 和 GSON 解析鸟廓。
9.1 使用 HTTP 協(xié)議訪問網(wǎng)絡(luò)
HTTP 協(xié)議从祝,其工作原理很簡(jiǎn)單:客戶端向服務(wù)器發(fā)出一條 HTTP 請(qǐng)求,服務(wù)器收到請(qǐng)求后會(huì)返回一些數(shù)據(jù)給客戶端引谜,然后客戶端再對(duì)這些數(shù)據(jù)進(jìn)行解析和處理牍陌。
9.1.1 使用 HttpURLConnection
下面學(xué)習(xí) HttpURLConnection 的用法,其請(qǐng)求步驟代碼如下:
/**
* HttpURLConnection 發(fā)送請(qǐng)求
*/
private void sendRequestWithHttpURLConnection() {
// 開啟線程來發(fā)送網(wǎng)絡(luò)請(qǐng)求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try{
URL url = new URL("http://www.baidu.com");
// 1. 獲取 HttpURLConnection 實(shí)例
connection = (HttpURLConnection) url.openConnection();
// 2. 設(shè)置請(qǐng)求方法
connection.setRequestMethod("GET");
// 3. 自由定制煌张,如設(shè)置連接超時(shí)呐赡、讀取超時(shí)等
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
// 4. 獲取服務(wù)器返回的輸入流
InputStream in = connection.getInputStream();
// 下面對(duì)獲取到的輸入流進(jìn)行讀取
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine())!= null){
response.append(line);
}
showResponse(response.toString());// 顯示請(qǐng)求結(jié)果
}catch (Exception e){
e.printStackTrace();
}finally {
if (reader != null){
try{
reader.close();
}catch (IOException e){
e.printStackTrace();
}
}
if (connection != null){
// 5.把 HTTP 連接關(guān)掉
connection.disconnect();
}
}
}
}).start();
}
別忘了聲明網(wǎng)絡(luò)權(quán)限:
<uses-permission android:name="android.permission.INTERNET" />
若是想要提交數(shù)據(jù)給服務(wù)器只需把請(qǐng)求方法改為 POST,并在獲取輸入流之前把要提交的數(shù)據(jù)寫出即可骏融。注意每條數(shù)據(jù)要以鍵值對(duì)的形式存在链嘀,數(shù)據(jù)與數(shù)據(jù)之間用 “&” 隔開,比如向服務(wù)器提交用戶名和密碼可寫成:
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");
9.1.2 使用 OKHttp
接下來學(xué)習(xí)下網(wǎng)絡(luò)請(qǐng)求開源項(xiàng)目 OKHttp档玻,其項(xiàng)目主頁地址是:https://github.com/square/okhttp
在使用 OKHttp 前怀泊,需要在項(xiàng)目中添加 OKHttp 庫的依賴,如下:
compile 'com.squareup.okhttp3:okhttp:3.5.0'
下面學(xué)習(xí) OKHttp 請(qǐng)求步驟误趴,如下:
/**
* OKHttp 發(fā)送請(qǐng)求
*/
private void sendRequestWithOKHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try{
// 1. 創(chuàng)建 OkHttpClient 實(shí)例
OkHttpClient client = new OkHttpClient();
// 2. 創(chuàng)建 Request 對(duì)象
Request request = new Request.Builder().url("http://www.baidu.com").build();
// 3. 調(diào)用 OkHttpClient 的 newCall() 方法來創(chuàng)建 Call 對(duì)象
Response response = client.newCall(request).execute();
// 4. 獲取返回的內(nèi)容
String responseData = response.body().string();
showResponse(responseData);// 顯示請(qǐng)求結(jié)果
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
相比 HttpURLConnection霹琼,OKHttp 簡(jiǎn)單易用,若是發(fā)起一條 POST 請(qǐng)求凉当,會(huì)比 GET 請(qǐng)求稍微復(fù)雜點(diǎn)枣申,需要構(gòu)建一個(gè) RequestBody 對(duì)象來存放待提交的參數(shù):
RequestBody requestBody = new FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build();
然后在 Request.Builder 中調(diào)用一下 post() 方法,并將 RequestBody 對(duì)象傳入:
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(RequestBody)
.build();
9.1.3 網(wǎng)絡(luò)編程的最佳實(shí)踐
在實(shí)際開發(fā)中看杭,我們通常將這些通用的網(wǎng)絡(luò)操作提取到一個(gè)公共類里忠藤,接下來就簡(jiǎn)單封裝下網(wǎng)絡(luò)操作。
首先針對(duì) HttpURLConnection 定義一個(gè)回調(diào)接口:
public interface HttpCallbackListener {
void onFinish(String response);// 請(qǐng)求成功時(shí)調(diào)用
void onError(Exception e);// 請(qǐng)求失敗時(shí)調(diào)用
}
接著編寫工具類 HttpUtil:
public class HttpUtil {
/**
* 用 HttpURLConnection 發(fā)送請(qǐng)求
* @param address
* @param listener
*/
public static void sendHttpRequest(final String address,final HttpCallbackListener listener){
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try{
URL url = new URL(address);
// 1. 獲取 HttpURLConnection 實(shí)例
connection = (HttpURLConnection) url.openConnection();
// 2. 設(shè)置請(qǐng)求方法
connection.setRequestMethod("GET");
// 3. 自由定制楼雹,如設(shè)置連接超時(shí)模孩、讀取超時(shí)等
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
// 4. 獲取服務(wù)器返回的輸入流
InputStream in = connection.getInputStream();
// 下面對(duì)獲取到的輸入流進(jìn)行讀取
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine())!= null){
response.append(line);
}
if (listener != null){
// 回調(diào) onFinish() 方法
listener.onFinish(response.toString());
}
}catch (Exception e){
if (listener != null){
// 回調(diào) onError() 方法
listener.onError(e);
}
}finally {
if (connection != null){
// 5.把 HTTP 連接關(guān)掉
connection.disconnect();
}
}
}
}).start();
}
/**
* 用 OKHttp 發(fā)送請(qǐng)求
* @param address
* @param callback
*/
public static void sendOKHttpRequest(String address, Callback callback){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
}
}
這時(shí)候用 HttpURLConnection 發(fā)送請(qǐng)求就可以寫成:
HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
// 在這里根據(jù)返回內(nèi)容執(zhí)行具體的邏輯
}
@Override
public void onError(Exception e) {
// 在這里對(duì)異常情況進(jìn)行處理
}
});
用 OKHttp 發(fā)送請(qǐng)求就可以寫成:
HttpUtil.sendOKHttpRequest("http://www.baidu.com", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 在這里對(duì)異常情況進(jìn)行處理
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 得到服務(wù)器返回的具體內(nèi)容
String responseData = response.body().string();
}
});
另外需要注意的是尖阔,不管是使用 HttpURLConnection 還是 OKHttp,最終回調(diào)接口都還是在子線程中運(yùn)行的榨咐。
下面舉個(gè)例子鞏固下介却,在布局中放置 Button 用于發(fā)送 HTTP 請(qǐng)求,放置一個(gè) TextView 用于顯示服務(wù)器返回的數(shù)據(jù)块茁,主要代碼如下:
public class HttpActivity extends AppCompatActivity implements View.OnClickListener {
private Button send_url_request,send_okHttp_request,clear_content;
private TextView response_text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_http);
response_text = (TextView) findViewById(R.id.response_text);
send_url_request = (Button) findViewById(R.id.send_url_request);
send_okHttp_request = (Button) findViewById(R.id.send_okHttp_request);
clear_content = (Button) findViewById(R.id.clear_content);
send_url_request.setOnClickListener(this);
send_okHttp_request.setOnClickListener(this);
clear_content.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.send_url_request:
sendRequestWithHttpURLConnection();
case R.id.send_okHttp_request:
sendRequestWithOKHttp();
case R.id.clear_content:
showResponse(""); //清空數(shù)據(jù)
}
}
/**
* OKHttp 發(fā)送請(qǐng)求
*/
private void sendRequestWithOKHttp() {
HttpUtil.sendOKHttpRequest("http://www.baidu.com", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 在這里對(duì)異常情況進(jìn)行處理
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 得到服務(wù)器返回的具體內(nèi)容
String responseData = response.body().string();
showResponse(responseData);
}
});
}
/**
* HttpURLConnection 發(fā)送請(qǐng)求
*/
private void sendRequestWithHttpURLConnection() {
HttpUtil.sendHttpRequest("http://www.baidu.com", new HttpCallbackListener() {
@Override
public void onFinish(String response) {
// 在這里根據(jù)返回內(nèi)容執(zhí)行具體的邏輯
showResponse(response);
}
@Override
public void onError(Exception e) {
// 在這里對(duì)異常情況進(jìn)行處理
}
});
}
/**
* 顯示請(qǐng)求結(jié)果
* @param response
*/
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在這里進(jìn)行UI 操作齿坷,將結(jié)果顯示到界面上
response_text.setText(response);
}
});
}
}
運(yùn)行效果如下:
9.2 解析 XML 格式數(shù)據(jù)
在網(wǎng)絡(luò)上傳輸數(shù)據(jù)時(shí)最常用的格式有兩種:XML 和 JSON。本節(jié)來學(xué)習(xí)下如何解析 XML 格式的數(shù)據(jù)龟劲。
解析 XML 格式的數(shù)據(jù)有多種方式胃夏,這里主要介紹 Pull 解析和 SAX 解析。解析前先來看看等下要解析的 XML 文本:
9.2.1 Pull 解析方式
Pull 解析整個(gè)過程比較簡(jiǎn)單昌跌,具體看代碼注釋:
/**
* pull 解析
* @param xmlData 要解析的xml數(shù)據(jù)
*/
private void parseXMLWithPull(String xmlData) {
try {
// 1. 獲取 XmlPullParserFactory 實(shí)例
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
// 2. 借助 XmlPullParserFactory 實(shí)例得到 XmlPullParser 對(duì)象
XmlPullParser xmlPullParser = factory.newPullParser();
// 3. 調(diào)用 setInput() 方法設(shè)置xml數(shù)據(jù)
xmlPullParser.setInput(new StringReader(xmlData));
// 4. 獲取當(dāng)前的解析事件
int eventType = xmlPullParser.getEventType();
String id = "";
String name = "";
String sex = "";
// 5. 通過 while 循環(huán)不斷地進(jìn)行解析
while (eventType != XmlPullParser.END_DOCUMENT){
String nodeName = xmlPullParser.getName();
switch (eventType){
// 開始解析某個(gè)節(jié)點(diǎn)
case XmlPullParser.START_TAG:
if ("id".equals(nodeName)){
id = xmlPullParser.nextText();
}else if ("name".equals(nodeName)){
name = xmlPullParser.nextText();
}else if ("sex".equals(nodeName)){
sex = xmlPullParser.nextText();
}
break;
// 完成解析某個(gè)節(jié)點(diǎn)
case XmlPullParser.END_TAG:
if ("student".equals(nodeName)){
Log.d("pull解析:", "id is" + id);
Log.d("pull解析:", "name is" + name);
Log.d("pull解析:", "sex is" + sex);
}
break;
default:
break;
}
// 獲取下一個(gè)解析事件
eventType = xmlPullParser.next();
}
}catch (Exception e){
e.printStackTrace();
}
}
9.2.2 SAX 解析方式
SAX 解析的用法比 Pull 解析要復(fù)雜些仰禀,但在語義方面會(huì)更加清楚。
用 SAX 解析需要建一個(gè)類繼承 DefaultHandler蚕愤,并重寫父類的5個(gè)方法答恶。為實(shí)現(xiàn)上面同樣的功能,新建一個(gè) ContentHandler 類萍诱,如下所示:
public class ContentHandler extends DefaultHandler {
private String nodeName;
private StringBuilder id;
private StringBuilder name;
private StringBuilder sex;
/**
* 開始 XML 解析時(shí)調(diào)用
* @throws SAXException
*/
@Override
public void startDocument() throws SAXException {
id = new StringBuilder();
name = new StringBuilder();
sex = new StringBuilder();
}
/**
* 開始解析某個(gè)節(jié)點(diǎn)時(shí)調(diào)用
* @param uri
* @param localName
* @param qName
* @param attributes
* @throws SAXException
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// 記錄當(dāng)前節(jié)點(diǎn)名
nodeName = localName;
}
/**
* 獲取節(jié)點(diǎn)中的內(nèi)容時(shí)調(diào)用
* @param ch
* @param start
* @param length
* @throws SAXException
*/
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 根據(jù)當(dāng)前節(jié)點(diǎn)名判斷將內(nèi)容添加到哪一個(gè) StringBuilder 對(duì)象中
if ("id".equals(nodeName)){
id.append(ch,start,length);
}else if ("name".equals(nodeName)){
name.append(ch,start,length);
}else if ("sex".equals(nodeName)){
sex.append(ch,start,length);
}
}
/**
* 完成解析某個(gè)節(jié)點(diǎn)時(shí)調(diào)用
* @param uri
* @param localName
* @param qName
* @throws SAXException
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("student".equals(localName)){
Log.d("sax解析:", "id is" + id.toString().trim());
Log.d("sax解析:", "name is" + name.toString().trim());
Log.d("sax解析:", "sex is" + sex.toString().trim());
// 最后要將 StringBuilder 清空掉
id.setLength(0);
name.setLength(0);
sex.setLength(0);
}
}
/**
* 完成整個(gè) XML 解析時(shí)調(diào)用
* @throws SAXException
*/
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
接下來就非常簡(jiǎn)單了悬嗓,代碼如下:
/**
* sax 解析
* @param xmlData
*/
private void parseXMLWithSAX(String xmlData){
try {
// 創(chuàng)建 SAXParserFactory 對(duì)象
SAXParserFactory factory = SAXParserFactory.newInstance();
// 獲取 XMLReader 對(duì)象
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
ContentHandler handler = new ContentHandler();
// 將 ContentHandler 的實(shí)例設(shè)置到 XMLReader 中
xmlReader.setContentHandler(handler);
// 開始執(zhí)行解析
xmlReader.parse(new InputSource(new StringReader(xmlData)));
}catch (Exception e){
e.printStackTrace();
}
}
9.2.3 舉個(gè)例子實(shí)在點(diǎn)
下面在布局中放置兩個(gè)按鈕,分別進(jìn)行pull解析和sax解析:
public class ParseXMLActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_pull,btn_sax;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_parse_xml);
btn_pull = (Button) findViewById(R.id.btn_pull);
btn_sax = (Button) findViewById(R.id.btn_sax);
btn_pull.setOnClickListener(this);
btn_sax.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_pull:
HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.xml", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//ToastUtils.showShort("請(qǐng)求失敗");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseData = response.body().string();
parseXMLWithPull(responseData); // pull 解析
}
});
break;
case R.id.btn_sax:
HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.xml", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//ToastUtils.showShort("請(qǐng)求失敗");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseData = response.body().string();
parseXMLWithSAX(responseData); // sax 解析
}
});
break;
}
}
/**
* pull 解析
* @param xmlData 要解析的xml數(shù)據(jù)
*/
private void parseXMLWithPull(String xmlData) { . . . }
/**
* sax 解析
* @param xmlData
*/
private void parseXMLWithSAX(String xmlData){ . . . }
}
運(yùn)行程序裕坊,打印的日志分別如下:
??可以看到包竹,已經(jīng)將 XML 數(shù)據(jù)成功解析出來了。
9.3 解析 JSON 數(shù)據(jù)
??類似的籍凝,解析 JSON 格式的數(shù)據(jù)有多種方式周瞎,這里主要介紹官方提供的 JSONObject 和谷歌的開源庫 GSON 來解析。解析前先來看看等下要解析的 JSON 文本:
9.3.1 使用 JSONObject
??使用 JSONObject 解析上面內(nèi)容比較簡(jiǎn)單饵蒂,具體看代碼:
/**
* 用 JSONObject 解析
* @param jsonData 需要解析的數(shù)據(jù)
*/
private void parseJSONWithJSONObject(String jsonData) {
try {
// 把需要解析的數(shù)據(jù)傳入到 JSONArray 對(duì)象中
JSONArray jsonArray = new JSONArray(jsonData);
for (int i = 0;i < jsonArray.length();i++){
JSONObject jsonObject = jsonArray.getJSONObject(i);
String id = jsonObject.getString("id");
String name = jsonObject.getString("name");
String sex = jsonObject.getString("sex");
Log.d("JSONObject解析", "id is "+id);
Log.d("JSONObject解析", "name is "+name);
Log.d("JSONObject解析", "sex is "+sex);
}
}catch (Exception e){
e.printStackTrace();
}
}
9.3.2 使用 GSON
??接下來學(xué)習(xí)下開源庫 GSON声诸,其項(xiàng)目主頁地址是:https://github.com/google/gson
??在使用 GSON 前,需要在項(xiàng)目中添加 GSON 庫的依賴退盯,如下:
compile 'com.google.code.gson:gson:2.8.0'
??GSON 可以將一段 JSON 格式的字符串自動(dòng)映射成一個(gè)對(duì)象彼乌,從而不需要手動(dòng)去編寫代碼進(jìn)行解析了。
??比如解析一段 JSON 格式數(shù)據(jù):
{"name":"Tom","age":20}
??就可以定義一個(gè) Person 類渊迁,并加入 name 和 age 兩字段慰照,然后只需調(diào)用如下代碼就可以將 JSON 數(shù)據(jù)自動(dòng)解析成一個(gè) Person 對(duì)象:
Gson gson = new Gson();
Person person = gson.fromJson(jsonData,Person.class);
??若解析一段 JSON 數(shù)組會(huì)麻煩些,需要借助 TypeToken 把期望解析成的數(shù)據(jù)類型傳入到 fromJson() 方法中:
List<Person> people = gson.fromJson(jsonData,new TypeToken<List<Person>>(){}.getType());
??GSON 的基本用法就是這樣琉朽。下面來解析上面的 JSON 文本毒租,首先新增一個(gè) Student 類:
public class Student {
private String id;
private String name;
private String sex;
// Getter and Setter
. . .
}
??接下來就非常簡(jiǎn)單了,代碼如下:
/**
* 用 GSON 解析
* @param jsonData
*/
private void parseJSONWithGSON(String jsonData){
Gson gson = new Gson();
List<Student>studentList = gson.fromJson(jsonData,new TypeToken<List<Student>>(){}.getType());
for (Student student:studentList){
Log.d("GSON解析", "id is "+student.getId());
Log.d("GSON解析", "name is "+student.getName());
Log.d("GSON解析", "sex is "+student.getSex());
}
}
9.3.3 舉個(gè)例子實(shí)在點(diǎn)
??下面在布局中放置兩個(gè)按鈕漓骚,分別用 JSONObject 和 GSON 進(jìn)行 json 解析:
public class ParseJSONActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_object,btn_gson;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_parse_json);
btn_object = (Button) findViewById(R.id.btn_object);
btn_gson = (Button) findViewById(R.id.btn_gson);
btn_object.setOnClickListener(this);
btn_gson.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_object:
HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.json", new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseData = response.body().string();
parseJSONWithJSONObject(responseData); // 用 JSONObject 解析
}
});
break;
case R.id.btn_gson:
HttpUtil.sendOKHttpRequest("http://10.0.2.2/get_data.json", new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseData = response.body().string();
parseJSONWithGSON(responseData); // 用 GSON 解析
}
});
break;
}
}
/**
* 用 JSONObject 解析
* @param jsonData 需要解析的數(shù)據(jù)
*/
private void parseJSONWithJSONObject(String jsonData) { . . . }
/**
* 用 GSON 解析
* @param jsonData
*/
private void parseJSONWithGSON(String jsonData){ . . . }
}
??運(yùn)行程序蝌衔,打印的日志分別如下:
??關(guān)于網(wǎng)絡(luò)編程先學(xué)習(xí)到這,下篇文章將進(jìn)入安卓四大組件之服務(wù)的學(xué)習(xí)蝌蹂。