本篇文章主要介紹以幾下個知識點:
- 使用 HTTP 協議訪問網絡:
使用 HttpURLConnection 和 OKHttp;- 解析 XML 格式數據:
Pull 和 SAX 解析;- 解析 JSON 數據:
JSONObject 和 GSON 解析。
9.1 使用 HTTP 協議訪問網絡
HTTP 協議,其工作原理很簡單:客戶端向服務器發出一條 HTTP 請求,服務器收到請求后會返回一些數據給客戶端,然后客戶端再對這些數據進行解析和處理。
9.1.1 使用 HttpURLConnection
下面學習 HttpURLConnection 的用法,其請求步驟代碼如下:
/**
* HttpURLConnection 發送請求
*/
private void sendRequestWithHttpURLConnection() {
// 開啟線程來發送網絡請求
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 實例
connection = (HttpURLConnection) url.openConnection();
// 2. 設置請求方法
connection.setRequestMethod("GET");
// 3. 自由定制,如設置連接超時、讀取超時等
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
// 4. 獲取服務器返回的輸入流
InputStream in = connection.getInputStream();
// 下面對獲取到的輸入流進行讀取
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine())!= null){
response.append(line);
}
showResponse(response.toString());// 顯示請求結果
}catch (Exception e){
e.printStackTrace();
}finally {
if (reader != null){
try{
reader.close();
}catch (IOException e){
e.printStackTrace();
}
}
if (connection != null){
// 5.把 HTTP 連接關掉
connection.disconnect();
}
}
}
}).start();
}
別忘了聲明網絡權限:
<uses-permission android:name="android.permission.INTERNET" />
若是想要提交數據給服務器只需把請求方法改為 POST,并在獲取輸入流之前把要提交的數據寫出即可。注意每條數據要以鍵值對的形式存在,數據與數據之間用 “&” 隔開,比如向服務器提交用戶名和密碼可寫成:
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");
9.1.2 使用 OKHttp
接下來學習下網絡請求開源項目 OKHttp,其項目主頁地址是:https://github.com/square/okhttp
在使用 OKHttp 前,需要在項目中添加 OKHttp 庫的依賴,如下:
compile 'com.squareup.okhttp3:okhttp:3.5.0'
下面學習 OKHttp 請求步驟,如下:
/**
* OKHttp 發送請求
*/
private void sendRequestWithOKHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try{
// 1. 創建 OkHttpClient 實例
OkHttpClient client = new OkHttpClient();
// 2. 創建 Request 對象
Request request = new Request.Builder().url("http://www.baidu.com").build();
// 3. 調用 OkHttpClient 的 newCall() 方法來創建 Call 對象
Response response = client.newCall(request).execute();
// 4. 獲取返回的內容
String responseData = response.body().string();
showResponse(responseData);// 顯示請求結果
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
相比 HttpURLConnection,OKHttp 簡單易用,若是發起一條 POST 請求,會比 GET 請求稍微復雜點,需要構建一個 RequestBody 對象來存放待提交的參數:
RequestBody requestBody = new FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build();
然后在 Request.Builder 中調用一下 post() 方法,并將 RequestBody 對象傳入:
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(RequestBody)
.build();
9.1.3 網絡編程的最佳實踐
在實際開發中,我們通常將這些通用的網絡操作提取到一個公共類里,接下來就簡單封裝下網絡操作。
首先針對 HttpURLConnection 定義一個回調接口:
public interface HttpCallbackListener {
void onFinish(String response);// 請求成功時調用
void onError(Exception e);// 請求失敗時調用
}
接著編寫工具類 HttpUtil:
public class HttpUtil {
/**
* 用 HttpURLConnection 發送請求
* @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 實例
connection = (HttpURLConnection) url.openConnection();
// 2. 設置請求方法
connection.setRequestMethod("GET");
// 3. 自由定制,如設置連接超時、讀取超時等
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
// 4. 獲取服務器返回的輸入流
InputStream in = connection.getInputStream();
// 下面對獲取到的輸入流進行讀取
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine())!= null){
response.append(line);
}
if (listener != null){
// 回調 onFinish() 方法
listener.onFinish(response.toString());
}
}catch (Exception e){
if (listener != null){
// 回調 onError() 方法
listener.onError(e);
}
}finally {
if (connection != null){
// 5.把 HTTP 連接關掉
connection.disconnect();
}
}
}
}).start();
}
/**
* 用 OKHttp 發送請求
* @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);
}
}
這時候用 HttpURLConnection 發送請求就可以寫成:
HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
// 在這里根據返回內容執行具體的邏輯
}
@Override
public void onError(Exception e) {
// 在這里對異常情況進行處理
}
});
用 OKHttp 發送請求就可以寫成:
HttpUtil.sendOKHttpRequest("http://www.baidu.com", 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();
}
});
另外需要注意的是,不管是使用 HttpURLConnection 還是 OKHttp,最終回調接口都還是在子線程中運行的。
下面舉個例子鞏固下,在布局中放置 Button 用于發送 HTTP 請求,放置一個 TextView 用于顯示服務器返回的數據,主要代碼如下:
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(""); //清空數據
}
}
/**
* OKHttp 發送請求
*/
private void sendRequestWithOKHttp() {
HttpUtil.sendOKHttpRequest("http://www.baidu.com", 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();
showResponse(responseData);
}
});
}
/**
* HttpURLConnection 發送請求
*/
private void sendRequestWithHttpURLConnection() {
HttpUtil.sendHttpRequest("http://www.baidu.com", new HttpCallbackListener() {
@Override
public void onFinish(String response) {
// 在這里根據返回內容執行具體的邏輯
showResponse(response);
}
@Override
public void onError(Exception e) {
// 在這里對異常情況進行處理
}
});
}
/**
* 顯示請求結果
* @param response
*/
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在這里進行UI 操作,將結果顯示到界面上
response_text.setText(response);
}
});
}
}
運行效果如下:
9.2 解析 XML 格式數據
在網絡上傳輸數據時最常用的格式有兩種:XML 和 JSON。本節來學習下如何解析 XML 格式的數據。
解析 XML 格式的數據有多種方式,這里主要介紹 Pull 解析和 SAX 解析。解析前先來看看等下要解析的 XML 文本:
9.2.1 Pull 解析方式
Pull 解析整個過程比較簡單,具體看代碼注釋:
/**
* pull 解析
* @param xmlData 要解析的xml數據
*/
private void parseXMLWithPull(String xmlData) {
try {
// 1. 獲取 XmlPullParserFactory 實例
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
// 2. 借助 XmlPullParserFactory 實例得到 XmlPullParser 對象
XmlPullParser xmlPullParser = factory.newPullParser();
// 3. 調用 setInput() 方法設置xml數據
xmlPullParser.setInput(new StringReader(xmlData));
// 4. 獲取當前的解析事件
int eventType = xmlPullParser.getEventType();
String id = "";
String name = "";
String sex = "";
// 5. 通過 while 循環不斷地進行解析
while (eventType != XmlPullParser.END_DOCUMENT){
String nodeName = xmlPullParser.getName();
switch (eventType){
// 開始解析某個節點
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;
// 完成解析某個節點
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;
}
// 獲取下一個解析事件
eventType = xmlPullParser.next();
}
}catch (Exception e){
e.printStackTrace();
}
}
9.2.2 SAX 解析方式
SAX 解析的用法比 Pull 解析要復雜些,但在語義方面會更加清楚。
用 SAX 解析需要建一個類繼承 DefaultHandler,并重寫父類的5個方法。為實現上面同樣的功能,新建一個 ContentHandler 類,如下所示:
public class ContentHandler extends DefaultHandler {
private String nodeName;
private StringBuilder id;
private StringBuilder name;
private StringBuilder sex;
/**
* 開始 XML 解析時調用
* @throws SAXException
*/
@Override
public void startDocument() throws SAXException {
id = new StringBuilder();
name = new StringBuilder();
sex = new StringBuilder();
}
/**
* 開始解析某個節點時調用
* @param uri
* @param localName
* @param qName
* @param attributes
* @throws SAXException
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// 記錄當前節點名
nodeName = localName;
}
/**
* 獲取節點中的內容時調用
* @param ch
* @param start
* @param length
* @throws SAXException
*/
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 根據當前節點名判斷將內容添加到哪一個 StringBuilder 對象中
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);
}
}
/**
* 完成解析某個節點時調用
* @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);
}
}
/**
* 完成整個 XML 解析時調用
* @throws SAXException
*/
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
接下來就非常簡單了,代碼如下:
/**
* sax 解析
* @param xmlData
*/
private void parseXMLWithSAX(String xmlData){
try {
// 創建 SAXParserFactory 對象
SAXParserFactory factory = SAXParserFactory.newInstance();
// 獲取 XMLReader 對象
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
ContentHandler handler = new ContentHandler();
// 將 ContentHandler 的實例設置到 XMLReader 中
xmlReader.setContentHandler(handler);
// 開始執行解析
xmlReader.parse(new InputSource(new StringReader(xmlData)));
}catch (Exception e){
e.printStackTrace();
}
}
9.2.3 舉個例子實在點
下面在布局中放置兩個按鈕,分別進行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("請求失敗");
}
@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("請求失敗");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseData = response.body().string();
parseXMLWithSAX(responseData); // sax 解析
}
});
break;
}
}
/**
* pull 解析
* @param xmlData 要解析的xml數據
*/
private void parseXMLWithPull(String xmlData) { . . . }
/**
* sax 解析
* @param xmlData
*/
private void parseXMLWithSAX(String xmlData){ . . . }
}
運行程序,打印的日志分別如下:
??可以看到,已經將 XML 數據成功解析出來了。
9.3 解析 JSON 數據
??類似的,解析 JSON 格式的數據有多種方式,這里主要介紹官方提供的 JSONObject 和谷歌的開源庫 GSON 來解析。解析前先來看看等下要解析的 JSON 文本:
9.3.1 使用 JSONObject
??使用 JSONObject 解析上面內容比較簡單,具體看代碼:
/**
* 用 JSONObject 解析
* @param jsonData 需要解析的數據
*/
private void parseJSONWithJSONObject(String jsonData) {
try {
// 把需要解析的數據傳入到 JSONArray 對象中
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
??接下來學習下開源庫 GSON,其項目主頁地址是:https://github.com/google/gson
??在使用 GSON 前,需要在項目中添加 GSON 庫的依賴,如下:
compile 'com.google.code.gson:gson:2.8.0'
??GSON 可以將一段 JSON 格式的字符串自動映射成一個對象,從而不需要手動去編寫代碼進行解析了。
??比如解析一段 JSON 格式數據:
{"name":"Tom","age":20}
??就可以定義一個 Person 類,并加入 name 和 age 兩字段,然后只需調用如下代碼就可以將 JSON 數據自動解析成一個 Person 對象:
Gson gson = new Gson();
Person person = gson.fromJson(jsonData,Person.class);
??若解析一段 JSON 數組會麻煩些,需要借助 TypeToken 把期望解析成的數據類型傳入到 fromJson() 方法中:
List<Person> people = gson.fromJson(jsonData,new TypeToken<List<Person>>(){}.getType());
??GSON 的基本用法就是這樣。下面來解析上面的 JSON 文本,首先新增一個 Student 類:
public class Student {
private String id;
private String name;
private String sex;
// Getter and Setter
. . .
}
??接下來就非常簡單了,代碼如下:
/**
* 用 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 舉個例子實在點
??下面在布局中放置兩個按鈕,分別用 JSONObject 和 GSON 進行 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 需要解析的數據
*/
private void parseJSONWithJSONObject(String jsonData) { . . . }
/**
* 用 GSON 解析
* @param jsonData
*/
private void parseJSONWithGSON(String jsonData){ . . . }
}
??運行程序,打印的日志分別如下:
??關于網絡編程先學習到這,下篇文章將進入安卓四大組件之服務的學習。