本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了,
每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以后閱讀和查閱,最后,感激感激郭霖先生提供這么好的書籍,
第9章 看看精彩的世界——使用網路技術
現在早已不是玩單機的時代了,無論是PC、手機、平板,還是電視,幾乎都會具備上網的功能,在可預見的未來,手表、眼鏡、汽車等設備也會逐個加入到這個行列,21世紀的確是互聯網的時代,
當然,Android手機肯定也是可以上網的,所以作為開發者,我們就需要考慮如何利用網路來撰寫出更加出色的應用程式,像QQ、微博、微信等常見的應用都會大量使用網路技術,
本章主要會講述如何在手機端使用HTTP協議和服務器端進行網路互動,并對服務器回傳的資料進行決議,這也是Android中最常使用到的網路技術,下面就讓我們一起來學習一下吧,
9.1 WebView的用法
比如說,要求在應用程式里展示一些網頁,加載和顯示網頁通常都是瀏覽器的任務,但是需求里又明確指出,不允許打開系統瀏覽器,而我們當然也不可能自己去撰寫一個瀏覽器出來,這時應該怎么辦呢?
Android早就已經考慮到了,并提供了一個WebView控制元件,借助它就可以在自己的應用程式里嵌入一個瀏覽器,從而非常輕松地展示各種各樣的網頁,WebView的用法也是相當簡單,下面我們就通過一個例子來學習一下吧,新建一個WebViewTest專案,然后修改activity_main.xml中的代碼,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_height="match_parent">
<WebView
android:layout_
android:layout_height="match_parent"
android:id="@+id/web_view"/>
</LinearLayout>
在布局檔案中使用到了一個新的控制元件:WebView,這個控制元件用來顯示網頁的,給它設定了一個id,并讓它充滿整個螢屏,然后修改MainActivity中的代碼,如下所示:
package com.zhouzhou.webviewtest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView =(WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl("https://www.cnblogs.com/1693977889zz/");
}
}
MainActivity中,首先使用findViewById()方法獲取到了WebView的實體,然后呼叫WebView的getSettings()方法可以去設定一些瀏覽器的屬性,這里只是呼叫了setJavaScriptEnabled()方法來讓WebView支持JavaScript腳本,
接下來,呼叫了WebView的setWebViewClient()方法,并傳入了一個WebViewClient的實體,這段代碼的作用是,當需要從一個網頁跳轉到另一個網頁時,我們希望目標網頁仍然在當前WebView中顯示,而不是打開系統瀏覽器,
最后一步,呼叫WebView的loadUrl()方法,并將網址傳入,即可展示相應網頁的內容,
另外還需要注意,由于本程式使用到了網路功能,而訪問網路是需要宣告權限的,因此我們還得修改AndroidManifest.xml檔案,并加入權限宣告,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhouzhou.webviewtest">
<uses-permission android:name="android.permission.INTERNET"/>
...
</manifest>
在開始運行之前,首先需要保證你的手機或模擬器是聯網的,如果你使用的是模擬器,只需保證電腦能正常上網即可,然后就可以運行一下程式了,效果如圖:

可以看到,WebViewTest這個程式現在已經具備了一個簡易瀏覽器的功能,不僅成功將我的博客展示了出來,還可以通過點擊鏈接瀏覽更多的網頁,
當然,WebView還有很多更加高級的使用技巧,就不再繼續進行探討了,這里先介紹了一下WebView的用法,只是希望你能對HTTP協議的使用有一個最基本的認識,接下來就要利用這個協議來做一些真正的網路開發作業,
9.2 使用HTTP協議訪問網路
如果說真的要去深入分析HTTP協議,可能需要花費整整一本書的篇幅,這里你只需要稍微了解一些就足夠了,它的作業原理特別簡單,就是客戶端向服務器發出一條HTTP請求,服務器收到請求之后會回傳一些資料給客戶端,然后客戶端再對這些資料進行決議和處理就可以了,
比如,上一節中使用到的WebView控制元件,其實也就是我們向服務器發起了一條HTTP請求,接著服務器分析出我們想要訪問的頁面,于是會把該網頁的HTML代碼進行回傳,然后WebView再呼叫手機瀏覽器的內核對回傳的HTML代碼進行決議,最終將頁面展示出來,
簡單來說,WebView已經在后臺幫我們處理好了發送HTTP請求、接收服務回應、決議回傳資料,以及最終的頁面展示這幾步作業,不過由于它封裝得實在是太好了,反而使得我們不能那么直觀地看出HTTP協議到底是如何作業的,因此,接下來就讓我們通過手動發送HTTP請求的方式,來更加深入地理解一下這個程序,
9.2.1 使用HttpURLConnection
在過去,Android上發送HTTP請求一般有兩種方式:HttpURLConnection和HttpClient,不過由于HttpClient存在API數量過多、擴展困難等缺點,Android團隊越來越不建議我們使用這種方式,
終于在Android 6.0系統中,HttpClient的功能被完全移除了,標志著此功能被正式棄用,因此本小節我們就學習一下現在官方建議使用的HttpURLConnection的用法,
首先需要獲取到HttpURLConnection的實體,一般只需new出一個URL物件,并傳入目標的網路地址,然后呼叫一下openConnection()方法即可,如下所示:
URL url = new URL("http://baidu.com");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
在得到了HttpURLConnection的實體之后,可以設定一下HTTP請求所使用的方法,常用的方法主要有兩個:GET和POST,GET表示希望從服務器那里獲取資料,而POST則表示希望提交資料給服務器,寫法如下:
connection.setRequestMethod("GET");
接下來,就可以進行一些自由的定制了,比如設定連接超時、讀取超時的毫秒數,以及服務器希望得到的一些訊息頭等,這部分內容根據自己的實際情況進行撰寫,示例寫法如下:
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
之后再呼叫getInputStream()方法就可以獲取到服務器回傳的輸入流了,剩下的任務就是對輸入流進行讀取,如下所示:
InputStream in = connection.getInputStream();
最后,可以呼叫disconnect()方法將這個HTTP連接關閉掉,如下所示:
connection.disconnect();
下面通過一個具體的例子來真正體驗一下HttpURLConnection的用法,新建一個NetworkTest專案,首先修改activity_main.xml中的代碼,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_
android:layout_height="match_parent">
<Button
android:layout_
android:layout_height="wrap_content"
android:id="@+id/send_request"
android:text="Send Request"/>
<ScrollView
android:layout_
android:layout_height="match_parent">
<TextView
android:layout_
android:layout_height="wrap_content"
android:id="@+id/response_text"/>
</ScrollView>
</LinearLayout>
使用了一個新的控制元件:ScrollView,它是用來做什么的呢?
由于手機螢屏的空間一般都比較小,有些時候過多的內容一屏是顯示不下的,借助ScrollView控制元件的話,我們就可以以滾動的形式查看螢屏外的那部分內容,另外,布局中還放置了一個Button和一個TextView, Button用于發送HTTP請求,TextView用于將服務器回傳的資料顯示出來,接著修改MainActivity中的代碼,如下所示:
package com.zhouzhou.networktest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button sendRequest = (Button) findViewById(R.id.send_request);
responseText = (TextView) findViewById(R.id.response_text);
sendRequest.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.send_request) {
sendRequestWithHttpURLConnection();
}
}
private void sendRequestWithHttpURLConnection() {
//開啟執行緒來發起網路請求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream inputStream = connection.getInputStream();
//下面對獲取到的輸入流進行讀取
reader = new BufferedReader(new InputStreamReader(inputStream));
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) {
connection.disconnect();
}
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在這里進行Ui操作,將結果顯示到界面
responseText.setText(response);
}
});
}
}
可以看到,在Send Request按鈕的點擊事件里呼叫了sendRequestWithHttpURLConnection()方法,在這個方法中先是開啟了一個子執行緒,然后在子執行緒里使用HttpURLConnection發出一條HTTP請求,請求的目標地址就是百度的首頁,
接著利用BufferedReader對服務器回傳的流進行讀取,并將結果傳入到了showResponse()方法中,而在showResponse()方法里則是呼叫了一個runOnUiThread()方法,然后在這個方法的匿名類引數中進行操作,將回傳的資料顯示到界面上,
那么這里為什么要用這個runOnUiThread()方法呢?這是因為Android是不允許在子執行緒中進行UI操作的,我們需要通過這個方法將執行緒切換到主執行緒,然后再更新UI元素,
關于這部分內容,我們將會在下一章中進行詳細講解,現在你只需要記得必須這么寫就可以了,完整的一套流程就是這樣,不過在開始運行之前,仍然別忘了要宣告網路權限,修改AndroidManifest.xml中的代碼,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhouzhou.networktest">
<uses-permission android:name="android.permission.INTERNET"/>
...
</manifest>
好了,現在運行一下程式,并點擊Send Request按鈕,結果如圖:

服務器回傳給我們的就是這種HTML代碼,只是通常情況下瀏覽器都會將這些代碼決議成漂亮的網頁后再展示出來,
那么如果是想要提交資料給服務器應該怎么辦呢?只需要將HTTP請求的方法改成POST,并在獲取輸入流之前把要提交的資料寫出即可,注意每條資料都要以鍵值對的形式存在,資料與資料之間用“&”符號隔開,比如說我們想要向服務器提交用戶名和密碼,就可以這樣寫:
connection.setRequestMethod("POST");
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()) ;
outputStream.writeBytes("username=admin&&password=123456");
9.2.2 使用OkHttp
當然,我們并不是只能使用HttpURLConnection,完全沒有任何其他選擇,事實上在開源盛行的今天,有許多出色的網路通信庫都可以替代原生的HttpURLConnection,而其中OkHttp無疑是做得最出色的一個,
OkHttp是由鼎鼎大名的Square公司開發的,這個公司在開源事業上面貢獻良多,除了OkHttp之外,還開發了像Picasso、Retrofit等著名的開源專案,
OkHttp不僅在介面封裝上面做得簡單易用,就連在底層實作上也是自成一派,比起原生的HttpURLConnection,可以說是有過之而無不及,現在已經成了廣大Android開發者首選的網路通信庫,
那么本小節我們就來學習一下OkHttp的用法,OkHttp的專案主頁地址是:https://github.com/square/okhttp,
See the project website for documentation and APIs.
HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.
OkHttp is an HTTP client that’s efficient by default:
- HTTP/2 support allows all requests to the same host to share a socket.
- Connection pooling reduces request latency (if HTTP/2 isn’t available).
- Transparent GZIP shrinks download sizes.
- Response caching avoids the network completely for repeat requests.
OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses, OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and services hosted in redundant data centers. OkHttp supports modern TLS features (TLS 1.3, ALPN, certificate pinning). It can be configured to fall back for broad connectivity.
Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks.
在使用OkHttp之前,我們需要先在專案中添加OkHttp庫的依賴,編輯app/build.gradle檔案,在dependencies閉包中添加如下內容:
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.9.3")
}
添加上述依賴會自動下載兩個庫,一個是OkHttp庫,一個是Okio庫,后者是前者的通信基礎,

下面我們來看一下OkHttp的具體用法,首先需要創建一個OkHttpClient的實體,如下所示:
OkHttpClient client = new OkHttpClient();
接下來,如果想要發起一條HTTP請求,就需要創建一個Request物件:
Request request = new Request.Builder().build();
當然,上述代碼只是創建了一個空的Request物件,并沒有什么實際作用,我們可以在最終的build()方法之前連綴很多其他方法來豐富這個Request物件,比如可以通過url()方法來設定目標的網路地址,如下所示:
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
之后,呼叫OkHttpClient的newCall()方法來創建一個Call物件,并呼叫它的execute()方法來發送請求并獲取服務器回傳的資料,寫法如下:
Response response = client.newCall(request).execute();
其中,Response物件就是服務器回傳的資料了,我們可以使用如下寫法來得到回傳的具體內容:
String responseData = https://www.cnblogs.com/1693977889zz/p/response.body().string();
如果是發起一條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("https://www.baidu.com")
.post(requestBody)
.build();
接下來的操作就和GET請求一樣了,呼叫execute()方法來發送請求并獲取服務器回傳的資料即可,
書中后面所有網路相關的功能我們都將會使用OkHttp來實作,到時候再進行進一步的學習,那么現在我們先把NetworkTest這個專案改用OkHttp的方式再實作一遍吧,由于布區域分完全不用改動,所以現在直接修改MainActivity中的代碼,如下所示:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
...
@Override
public void onClick(View view) {
if (view.getId() == R.id.send_request) {
//sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
Response response = client.newCall(request).execute();
String responseData = https://www.cnblogs.com/1693977889zz/p/response.body().string();
showResponse(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
...
}
這里并沒有做太多的改動,只是添加了一個sendRequestWithOkHttp()方法,并在Send Request按鈕的點擊事件里去呼叫這個方法,在這個方法中同樣還是先開啟了一個子執行緒,然后在子執行緒里使用OkHttp發出一條HTTP請求,請求的目標地址還是百度的首頁,OkHttp的用法也正如前面所介紹的一樣,
最后仍然還是呼叫了showResponse()方法來將服務器回傳的資料顯示到界面上,重新運行一下程式了,點擊SendRequest按鈕后,測驗結果OK,由此證明,使用OkHttp來發送HTTP請求的功能也已經成功實作了,
9.3 決議XML格式資料
通常情況下,每個需要訪問網路的應用程式都會有一個自己的服務器,我們可以向服務器提交資料,也可以從服務器上獲取資料,
不過這個時候就出現了一個問題,這些資料到底要以什么樣的格式在網路上傳輸呢?隨便傳遞一段文本肯定是不行的,因為另一方根本就不會知道這段文本的用途是什么,因此,一般我們都會在網路上傳輸一些格式化后的資料,這種資料會有一定的結構規格和語意,當另一方收到資料訊息之后就可以按照相同的結構規格進行決議,從而取出他想要的那部分內容,在網路上傳輸資料時最常用的格式有兩種:XML和JSON,下面我們就來一個一個地進行學習,本節首先學習一下如何決議XML格式的資料,
在開始之前我們還需要先解決一個問題,就是從哪兒才能獲取一段XML格式的資料呢?
這里我準備教你搭建一個最簡單的Web服務器,在這個服務器上提供一段XML文本,然后我們在程式里去訪問這個服務器,再對得到的XML文本進行決議,
搭建Web服務器其實非常簡單,有很多的服務器型別可供選擇,這里我準備使用Apache服務器,首先你需要去下載一個Apache服務器的安裝包,官方下載地址是:http://httpd.apache.org/download.cgi,如果你在這個網址中找不到Windows版的安裝包,也可以直接在百度上搜索“Apache服務器下載”,將會找到很多下載鏈接,
下面簡介,官方下載步驟:
- 點擊鏈接 a number of third party vendors.:

- 找到Downloading Apache for Windows,點擊ApacheHaus鏈接:

- 點擊紅框中的圖示即可開始下載,x86是32位的,x64是64位的,根據自己的作業系統選擇下載:

- 點擊之后跳轉到下載頁面,Your download will start shortly...

- 經常會超時哦~所以,我在網上下載的【Apache HTTP Server 官方版 v2.4.17】,保存到想要保存的位置(我保存到了:F:\ApacheServer),解壓壓縮包,打開httpd.conf檔案(我在F:\ApacheServer\conf),修改Apache安裝目錄,(其中“${SRVROOT}”指定義的SRVROOT路徑變數):

- 若80埠被占用(可在cmd下用命令netstat -an -o | findstr 80),則將80埠改為別的保存:

-
以管理員身份運行cmd,復制輸入"F:\ApacheServer\bin\httpd.exe" -k install -n apache,該命令意思是安裝這個目錄(F:\ApacheServer\bin\)下的Apache服務,并將該服務命名為“apache”,
也可以,以管理員身份運行cmd,進入到F:\ApacheServer\bin目錄下輸入 httpd.exe -k install -n apache安裝apache服務

- 雙擊運行bin目錄下的ApacheMonitor.exe,也可通過服務打開:

- 雙擊打開視窗界面,會看到如圖所示:

10. 點擊 start,測驗Apache服務器是否可用,在瀏覽器中輸入http://localhost:8099,(80埠被占用,修改成了8099)出現下面頁面,表示安裝配置成功!

接下來進入到F:\ApacheServer\htdocs目錄下,在這里新建一個名為get_data.xml的檔案,然后編輯這個檔案,并加入如下XML格式的內容:
<apps>
<app>
<id>1</id>
<name>Google Maps</name>
<version>1.0</version>
</app>
<app>
<id>2</id>
<name>Chrome</name>
<version>2.1</version>
</app>
<app>
<id>3</id>
<name>Google Play</name>
<version>2.3</version>
</app>
</apps>
這時在瀏覽器中訪問http://127.0.0.1:8099/get_data.xml這個網址,就應該出現如圖:

準備作業到此結束,接下來就讓我們在Android程式里去獲取并決議這段XML資料吧,
9.3.1 Pull決議方式
決議XML格式的資料其實也有挺多種方式的,本節中我們學習比較常用的兩種,Pull決議和SAX決議,
那么簡單起見,這里仍然是在NetworkTest專案的基礎上繼續開發,這樣我們就可以重用之前網路通信部分的代碼,從而把作業的重心放在XML資料決議上,
既然XML格式的資料已經提供好了,現在要做的就是從中決議出我們想要得到的那部分內容,修改MainActivity中的代碼,如下所示:
package com.zhouzhou.networktest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button sendRequest = (Button) findViewById(R.id.send_request);
responseText = (TextView) findViewById(R.id.response_text);
sendRequest.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.send_request) {
//sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://127.0.0.1:8099/get_data.xml")
.build();
Response response = client.newCall(request).execute();
String responseData = https://www.cnblogs.com/1693977889zz/p/response.body().string();
//showResponse(responseData);
parseXMLWithPull(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private void parseXMLWithPull(String xmlData) {
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(new StringReader(xmlData));
int eventType = xmlPullParser.getEventType();
String id ="";
String name = "";
String version = "";
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 ("version".equals(nodeName)) {
version = xmlPullParser.nextText();
}
break;
}
//完成決議某個節點
case XmlPullParser.END_TAG: {
if ("app".equals(nodeName)) {
Log.d("MainActivity","id is " + id);
Log.d("MainActivity","name is " + name);
Log.d("MainActivity","version is " + version);
}
break;
}
default:
break;
}
eventType = xmlPullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendRequestWithHttpURLConnection() {
//開啟執行緒來發起網路請求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream inputStream = connection.getInputStream();
//下面對獲取到的輸入流進行讀取
reader = new BufferedReader(new InputStreamReader(inputStream));
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) {
connection.disconnect();
}
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在這里進行Ui操作,將結果顯示到界面
responseText.setText(response);
}
});
}
}
可以看到,這里首先是將HTTP請求的地址改成了http://10.0.2.2:8099/get_data.xml,
10.0.2.2對于模擬器來說就是電腦本機的IP地址,如果用:http://127.0.0.1:8099/get_data.xml或者http://localhost:8099/get_data.xml 的話,報出的錯誤資訊是:java.net.ConnectException: failed to connect to /127.0.0.1 (port 8099) after 2500ms: isConnected failed: ECONNREFUSED (Connection refused)

另外,可能報錯:W/System.err: java.net.UnknownServiceException:
CLEARTEXT communication 你的域名 not permitted by network security policy.

此時的報錯原因是,網路安全策略不允許:
為保證用戶資料和設備的安全,Google針對下一代 Android 系統(Android P) 的應用程式,將要求默認使用加密連接,這意味著 Android P 將禁止 App 使用所有未加密的連接,因此運行 Android P 系統的安卓設備無論是接識訓者發送流量,未來都不能明碼傳輸,需要使用下一代(Transport Layer Security)傳輸層安全協議,而 Android Nougat 和 Oreo 則不受影響,
在Android P系統的設備上,如果應用使用的是非加密的明文流量的http網路請求,則會導致該應用無法進行網路請求,https則不會受影響,同樣地,如果應用嵌套了webview,webview也只能使用https請求,
解決辦法:
(1)APP改用https請求
(2)targetSdkVersion 降到27以下(此問題發生在 API>=27 的新專案工程)
(3)在 application 元素中添加:
android:usesCleartextTraffic="true"
更規范一點是:
在res目錄下新建xml目錄,在xml目錄下新增 network_security_config.xml 檔案,內容:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
再在 AndroidManifest.xml 的 Application 標簽中新增屬性:
android:networkSecurityConfig="@xml/network_security_config"
報錯全部解決,程式運行OK,繼續,
在得到了服務器回傳的資料后,我們并不再直接將其展示,而是呼叫了parseXMLWithPull()方法來決議服務器回傳的資料,parseXMLWithPull()方法中的代碼,首先要獲取到一個XmlPullParserFactory的實體,并借助這個實體得到XmlPullParser物件,然后呼叫XmlPullParser的setInput()方法將服務器回傳的XML資料設定進去就可以開始決議了,決議的程序也非常簡單,通過getEventType()可以得到當前的決議事件,然后在一個while回圈中不斷地進行決議,如果當前的決議事件不等于XmlPullParser.END_DOCUMENT,說明決議作業還沒完成,呼叫next()方法后可以獲取下一個決議事件,
在while回圈中,我們通過getName()方法得到當前節點的名字,如果發現節點名等于id、name或version,就呼叫nextText()方法來獲取節點內具體的內容,每當決議完一個app節點后就將獲取到的內容列印出來,好了
整體的程序就是這么簡單,下面就讓我們來測驗一下吧,運行NetworkTest專案,然后點擊Send Request按鈕,觀察logcat中的列印日志,如圖:

9.3.2 SAX決議方式
Pull決議方式雖然非常好用,但它并不是我們唯一的選擇,SAX決議也是一種特別常用的XML決議方式,雖然它的用法比Pull決議要復雜一些,但在語意方面會更加清楚,通常情況下我們都會新建一個類繼承自DefaultHandler,并重寫父類的5個方法,如下所示:
public class MyHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
super.startDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
startDocument()方法會在開始XML決議的時候呼叫;
startElement()方法會在開始決議某個節點的時候呼叫;
characters()方法會在獲取節點中內容的時候呼叫;
endElement()方法會在完成決議某個節點的時候呼叫;
endDocument()方法會在完成整個XML決議的時候呼叫,
其中,startElement()、characters()和endElement()這3個方法是有引數的,從XML中決議出的資料就會以引數的形式傳入到這些方法中,
需要注意的是,在獲取節點中的內容時,characters()方法可能會被呼叫多次,一些換行符也被當作內容決議出來,我們需要針對這種情況在代碼中做好控制,
那么下面就讓我們嘗試用SAX決議的方式來實作和上一小節中同樣的功能吧,新建一個ContentHandler類繼承自DefaultHandler,并重寫父類的5個方法,如下所示:
package com.zhouzhou.networktest;
import android.util.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class ContentHandler extends DefaultHandler {
private String nodeName;
private StringBuilder id;
private StringBuilder name;
private StringBuilder version;
// startDocument()方法會在開始XML決議的時候呼叫;
@Override
public void startDocument() throws SAXException {
super.startDocument();
id = new StringBuilder();
name = new StringBuilder();
version = new StringBuilder();
}
// startElement()方法會在開始決議某個節點的時候呼叫;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
//記錄當前節點名
nodeName = localName;
}
// characters()方法會在獲取節點中內容的時候呼叫;
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
// 根據當前的節點名判斷將內容添加到哪一個StringBuilder物件中
if ("id".equals(nodeName)) {
id.append(ch,start,length);
} else if ("name".equals(nodeName)) {
name.append(ch,start,length);
} else if ("version".equals(nodeName)) {
version.append(ch,start,length);
}
}
// endElement()方法會在完成決議某個節點的時候呼叫;
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if ("app".equals(localName)) {
Log.d("ContentHandler","id is " + id.toString().trim());
Log.d("ContentHandler","name is " + name.toString().trim());
Log.d("ContentHandler","version is " + version.toString().trim());
//最后要將StringBuilder清空掉
id.setLength(0);
name.setLength(0);
version.setLength(0);
}
}
// endDocument()方法會在完成整個XML決議的時候呼叫,
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
可以看到,我們首先給id、name和version節點分別定義了一個StringBuilder物件,并在startDocument()方法里對它們進行了初始化,
每當開始決議某個節點的時候,startElement()方法就會得到呼叫,其中localName引數記錄著當前節點的名字,這里我們把它記錄下來,
接著在決議節點中具體內容的時候就會呼叫characters()方法,我們會根據當前的節點名進行判斷,將決議出的內容添加到哪一個StringBuilder物件中,
最后在endElement()方法中進行判斷,如果app節點已經決議完成,就列印出id、name和version的內容,
需要注意的是,目前id、name和version中都可能是包括回車或換行符的,因此在列印之前我們還需要呼叫一下trim()方法,并且列印完成后還要將StringBuilder的內容清空掉,不然的話會影響下一次內容的讀取,接下來的作業就非常簡單了,修改MainActivity中的代碼,如下所示:
package com.zhouzhou.networktest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.xml.parsers.SAXParserFactory;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button sendRequest = (Button) findViewById(R.id.send_request);
responseText = (TextView) findViewById(R.id.response_text);
sendRequest.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.send_request) {
//sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
//指定訪問的服務器地址是本機電腦
// 模擬器默認把127.0.0.1和localhost當做本身了,在模擬器上可以用10.0.2.2代替127.0.0.1和localhost
.url("http://10.0.2.2:8099/get_data.xml")
.build();
Response response = client.newCall(request).execute();
String responseData = https://www.cnblogs.com/1693977889zz/p/response.body().string();
//showResponse(responseData);
//parseXMLWithPull(responseData);
parseXMLWithSAX(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private void parseXMLWithSAX(String xmlData) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
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();
}
}
private void parseXMLWithPull(String xmlData) {
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(new StringReader(xmlData));
int eventType = xmlPullParser.getEventType();
String id ="";
String name = "";
String version = "";
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 ("version".equals(nodeName)) {
version = xmlPullParser.nextText();
}
break;
}
//完成決議某個節點
case XmlPullParser.END_TAG: {
if ("app".equals(nodeName)) {
Log.d("MainActivity","id is " + id);
Log.d("MainActivity","name is " + name);
Log.d("MainActivity","version is " + version);
}
break;
}
default:
break;
}
eventType = xmlPullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendRequestWithHttpURLConnection() {
//開啟執行緒來發起網路請求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream inputStream = connection.getInputStream();
//下面對獲取到的輸入流進行讀取
reader = new BufferedReader(new InputStreamReader(inputStream));
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) {
connection.disconnect();
}
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在這里進行Ui操作,將結果顯示到界面
responseText.setText(response);
}
});
}
}
在得到了服務器回傳的資料后,我們這次去呼叫parseXMLWithSAX()方法來決議XML資料,parseXMLWithSAX()方法中先是創建了一個SAXParserFactory的物件,然后再獲取到XMLReader物件,接著將我們撰寫的ContentHandler的實體設定到XMLReader中,最后呼叫parse()方法開始執行決議就好了,
現在重新運行一下程式,點擊Send Request按鈕后觀察logcat中的列印日志,你會看到和上面一樣的結果,

除了Pull決議和SAX決議之外,其實還有一種DOM決議方式也算挺常用的,不過這里我們就不再展開進行講解了,感興趣的話你可以自己去查閱一下相關資料,
9.4 決議JSON格式資料
如何決議JSON格式的資料了,比起XML, JSON的主要優勢在于它的體積更小,在網路上傳輸的時候可以更省流量,但缺點在于,它的語意性較差,看起來不如XML直觀,
在開始之前,還需要在F:\ApacheServer\htdocs目錄中新建一個get_data.json的檔案,然后編輯這個檔案,并加入如下JSON格式的內容:
[{"id":"5","version":"5.5","name":"Clash of Clans"},
{"id":"6","version":"7.0","name":"Boom Beach"},
{"id":"7","version":"3.5","name":"Clash Royale"}]
這時在瀏覽器中訪問http://127.0.0.1:8099/get_data.json這個網址,就應該出現如圖:

把JSON格式的資料也準備好了,下面就開始學習如何在Android程式中決議這些資料吧,
9.4.1 使用JSONObject
類似地,決議JSON資料也有很多種方法,可以使用官方提供的JSONObject,也可以使用谷歌的開源庫GSON,另外,一些第三方的開源庫如Jackson、FastJSON等也非常不錯,本節中我們就來學習一下前兩種決議方式的用法,修改MainActivity中的代碼,如下所示:
package com.zhouzhou.networktest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONObject;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.xml.parsers.SAXParserFactory;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button sendRequest = (Button) findViewById(R.id.send_request);
responseText = (TextView) findViewById(R.id.response_text);
sendRequest.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.send_request) {
//sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
//指定訪問的服務器地址是本機電腦
// 模擬器默認把127.0.0.1和localhost當做本身了,在模擬器上可以用10.0.2.2代替127.0.0.1和localhost
.url("http://10.0.2.2:8099/get_data.json")
.build();
Response response = client.newCall(request).execute();
String responseData = https://www.cnblogs.com/1693977889zz/p/response.body().string();
//showResponse(responseData);
//parseXMLWithPull(responseData);
//parseXMLWithSAX(responseData);
parseJSONWithJSONObject(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private void parseJSONWithJSONObject(String jsonData) {
try {
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 version = jsonObject.getString("version");
Log.d("MainActivity","id is " + id);
Log.d("MainActivity","name is " + name);
Log.d("MainActivity","version is " + version);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void parseXMLWithSAX(String xmlData) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
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();
}
}
private void parseXMLWithPull(String xmlData) {
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(new StringReader(xmlData));
int eventType = xmlPullParser.getEventType();
String id = "";
String name = "";
String version = "";
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 ("version".equals(nodeName)) {
version = xmlPullParser.nextText();
}
break;
}
//完成決議某個節點
case XmlPullParser.END_TAG: {
if ("app".equals(nodeName)) {
Log.d("MainActivity","id is " + id);
Log.d("MainActivity","name is " + name);
Log.d("MainActivity","version is " + version);
}
break;
}
default:
break;
}
eventType = xmlPullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendRequestWithHttpURLConnection() {
//開啟執行緒來發起網路請求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream inputStream = connection.getInputStream();
//下面對獲取到的輸入流進行讀取
reader = new BufferedReader(new InputStreamReader(inputStream));
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) {
connection.disconnect();
}
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在這里進行Ui操作,將結果顯示到界面
responseText.setText(response);
}
});
}
}
現在重新運行一下程式,并點擊Send Request按鈕,結果如圖:

9.4.2 使用GSON
如果你認為使用JSONObject來決議JSON資料已經非常簡單了,那你就太容易滿足了,谷歌提供的GSON開源庫(https://github.com/google/gson/)可以讓決議JSON資料的作業簡單到讓你不敢想象的地步,那我們肯定是不能錯過這個學習機會的,
不過GSON并沒有被添加到Android官方的API中,因此如果想要使用這個功能的話,就必須要在專案中添加GSON庫的依賴,編輯app/build.gradle檔案,在dependencies閉包中添加如下內容:
dependencies {
implementation 'com.google.code.gson:gson:2.9.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> appList = gson.fromJson(gsonData,new TypeToken<List<Person>>(){}.getType());
基本的用法就是這樣,下面就讓我們來真正地嘗試一下吧,首先新增一個App類,并加入id、name和version這3個欄位,如下所示:
package com.zhouzhou.networktest;
public class App {
private String id;
private String name;
private String version;
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 String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
然后修改MainActivity中的代碼,如下所示:
package com.zhouzhou.networktest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.json.JSONArray;
import org.json.JSONObject;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import javax.xml.parsers.SAXParserFactory;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView responseText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button sendRequest = (Button) findViewById(R.id.send_request);
responseText = (TextView) findViewById(R.id.response_text);
sendRequest.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.send_request) {
//sendRequestWithHttpURLConnection();
sendRequestWithOkHttp();
}
}
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
//指定訪問的服務器地址是本機電腦
// 模擬器默認把127.0.0.1和localhost當做本身了,在模擬器上可以用10.0.2.2代替127.0.0.1和localhost
.url("http://10.0.2.2:8099/get_data.json")
.build();
Response response = client.newCall(request).execute();
String responseData = https://www.cnblogs.com/1693977889zz/p/response.body().string();
//showResponse(responseData);
//parseXMLWithPull(responseData);
//parseXMLWithSAX(responseData);
//parseJSONWithJSONObject(responseData);
parseJSONWithGSON(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private void parseJSONWithGSON(String gsonData) {
Gson gson = new Gson();
List appList = gson.fromJson(gsonData,new TypeToken>(){}.getType());
for (App app : appList) {
Log.d("MainActivity","id is " + app.getId());
Log.d("MainActivity","name is " + app.getName());
Log.d("MainActivity","version is " + app.getVersion());
}
}
private void parseJSONWithJSONObject(String jsonData) {
try {
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 version = jsonObject.getString("version");
Log.d("MainActivity","id is " + id);
Log.d("MainActivity","name is " + name);
Log.d("MainActivity","version is " + version);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void parseXMLWithSAX(String xmlData) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
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();
}
}
private void parseXMLWithPull(String xmlData) {
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(new StringReader(xmlData));
int eventType = xmlPullParser.getEventType();
String id = "";
String name = "";
String version = "";
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 ("version".equals(nodeName)) {
version = xmlPullParser.nextText();
}
break;
}
//完成決議某個節點
case XmlPullParser.END_TAG: {
if ("app".equals(nodeName)) {
Log.d("MainActivity","id is " + id);
Log.d("MainActivity","name is " + name);
Log.d("MainActivity","version is " + version);
}
break;
}
default:
break;
}
eventType = xmlPullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendRequestWithHttpURLConnection() {
//開啟執行緒來發起網路請求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream inputStream = connection.getInputStream();
//下面對獲取到的輸入流進行讀取
reader = new BufferedReader(new InputStreamReader(inputStream));
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) {
connection.disconnect();
}
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在這里進行Ui操作,將結果顯示到界面
responseText.setText(response);
}
});
}
}
現在重新運行程式,點擊Send Request按鈕后觀察logcat中的列印日志,測驗OK!
9.5 網路編程的最佳實踐
也許你還沒有發現,之前我們的寫法其實是很有問題的,因為一個應用程式很可能會在許多地方都使用到網路功能,而發送HTTP請求的代碼基本都是相同的,如果我們每次都去撰寫一遍發送HTTP請求的代碼,這顯然是非常差勁的做法,
沒錯,通常情況下我們都應該將這些通用的網路操作提取到一個公共的類里,并提供一個靜態方法,當想要發起網路請求的時候,只需簡單地呼叫一下這個方法即可,比如使用如下的寫法:
package com.zhouzhou.networktest;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpUtil {
public static String sendHttpRequest(String address) {
HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}
以后每當需要發起一條HTTP請求的時候就可以這樣寫:
String address = "http://www.baidu.com"
String response = HttpUtil.sendHttpRequest(address);
在獲取到服務器回應的資料后,我們就可以對它進行決議和處理了,但是需要注意,網路請求通常都是屬于耗時操作,而sendHttpRequest()方法的內部并沒有開啟執行緒,這樣就有可能導致在呼叫sendHttpRequest()方法的時候使得主執行緒被阻塞住,
你可能會說,很簡單嘛,在sendHttpRequest()方法內部開啟一個執行緒不就解決這個問題了嗎?其實沒有你想象中的那么容易,因為如果我們在sendHttpRequest()方法中開啟了一個執行緒來發起HTTP請求,那么服務器回應的資料是無法進行回傳的,所有的耗時邏輯都是在子執行緒里進行的,sendHttpRequest()方法會在服務器還沒來得及回應的時候就執行結束了,當然也就無法回傳回應的資料了,
那么遇到這種情況時應該怎么辦呢?其實解決方法并不難,只需要使用Java的回呼機制就可以了,下面就讓我們來學習一下回呼機制到底是如何使用的,首先需要定義一個介面,比如將它命名成HttpCallbackListener,代碼如下所示:
package com.zhouzhou.networktest;
public interface HttpCallbackListener {
void onFinish(String response);
void one rror(Exception e);
}
可以看到,我們在介面中定義了兩個方法,onFinish()方法表示當服務器成功回應我們請求的時候呼叫,onError()表示當進行網路操作出現錯誤的時候呼叫,這兩個方法都帶有引數,onFinish()方法中的引數代表著服務器回傳的資料,而onError()方法中的引數記錄著錯誤的詳細資訊,接著修改HttpUtil中的代碼,如下所示:
package com.zhouzhou.networktest;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpUtil {
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);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
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) {
e.printStackTrace();
// 回呼onError()方法
listener.onError(e);
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
}
首先給sendHttpRequest()方法添加了一個HttpCallbackListener引數,并在方法的內部開啟了一個子執行緒,然后在子執行緒里去執行具體的網路操作,
注意,子執行緒中是無法通過return陳述句來回傳資料的,因此這里我們將服務器回應的資料傳入了HttpCallbackListener的onFinish()方法中,如果出現了例外就將例外原因傳入到onError()方法中,
現在sendHttpRequest()方法接收兩個引數了,因此我們在呼叫它的時候還需要將HttpCallbackListener的實體傳入,如下所示:
HttpUtil.sendHttpRequest(address,new HttpCallbackListener(){
@Override
public void onFinish(String response) {
//在這里根據回傳內容執行具體的邏輯
}
@Override
public void one rror(Exception e) {
//在這里對例外情況進行處理
}
});
這樣的話,當服務器成功回應的時候,我們就可以在onFinish()方法里對回應資料進行處理了,類似地,如果出現了例外,就可以在onError()方法里對例外情況進行處理,
如此一來,我們就巧妙地利用回呼機制將回應資料成功回傳給呼叫方了,不過你會發現,上述使用HttpURLConnection的寫法總體來說還是比較復雜的,那么使用OkHttp會變得簡單嗎?答案是肯定的,而且要簡單得多,下面我們來具體看一下,在HttpUtil中加入一個sendOkHttpRequest()方法,如下所示:
public class HttpUtil {
...
public static void sendOkHttpRequest(String address,okhttp3.Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(address)
.build();
client.newCall(request).enqueue(callback);
}
}
可以看到,sendOkHttpRequest()方法中有一個okhttp3.Callback引數,這個是OkHttp庫中自帶的一個回呼介面,類似于我們剛才自己撰寫的HttpCallbackListener,
然后在client. newCall()之后沒有像之前那樣一直呼叫execute()方法,而是呼叫了一個enqueue()方法,并把okhttp3.Callback引數傳入,OkHttp在enqueue()方法的內部已經幫我們開好子執行緒了,然后會在子執行緒中去執行HTTP請求,并將最終的請求結果回呼到okhttp3.Callback當中,那么我們在呼叫sendOkHttpRequest()方法的時候就可以這樣寫:
HttpUtil.sendOkHttpRequest("http://www.baidu.com",new okhttp3.Callback(){
@Override
public void onResponse(Call call,Response response) throws IOException {
//得到服務器回傳的具體內容
String responseData = https://www.cnblogs.com/1693977889zz/p/response.body().string();
}
@Override
public void onFailure(Call call,IOException e) {
//在這里對例外情況進行處理
}
});
由此可以看出,OkHttp的介面設計得確實非常人性化,它將一些常用的功能進行了很好的封裝,使得我們只需撰寫少量的代碼就能完成較為復雜的網路操作,當然這并不是OkHttp的全部,后面我們還會繼續學習它的其他相關知識,
另外需要注意的是,不管是使用HttpURLConnection還是OkHttp,最終的回呼介面都還是在子執行緒中運行的,因此我們不可以在這里執行任何的UI操作,除非借助runOnUiThread()方法來進行執行緒轉換,至于具體的原因,我們很快就會在下一章中學習到了,
9.6 小結與點評
本章中主要學習了在Android中使用HTTP協議來進行網路互動的知識,雖然Android中支持的網路通信協議有很多種,但HTTP協議無疑是最常用的一種,通常我們有兩種方式來發送HTTP請求,分別是HttpURLConnection和OkHttp,
接著又學習了XML和JSON格式資料的決議方式,無論是XML還是JSON,它們各自又擁有多種決議方式,如果以后作業中還需要用到其他的決議方式,可以自行去學習,
最后,主要學習了如何利用Java的回呼機制來將服務器回應的資料進行回傳,其實除此之外,還有很多地方都可以使用到Java的回呼機制,希望能舉一反三,
個人學習筆記,針對本人在自學中遇到的問題,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/472409.html
標籤:Android
