《第一行代碼:Android篇》學習筆記(九)

来源:https://www.cnblogs.com/1693977889zz/archive/2022/05/11/16256363.html
-Advertisement-
Play Games

本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,感激 ...


本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼: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_width="match_parent"
    android:layout_height="match_parent">
    <WebView
        android:layout_width="match_parent"
        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>

在開始運行之前,首先需要保證你的手機或模擬器是聯網的,如果你使用的是模擬器,只需保證電腦能正常上網即可。然後就可以運行一下程式了,效果如圖:

image

可以看到,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_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/send_request"
        android:text="Send Request"/>
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            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按鈕,結果如圖:

image

伺服器返回給我們的就是這種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庫,後者是前者的通信基礎。

image

下麵我們來看一下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 = 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 = 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伺服器下載”,將會找到很多下載鏈接。

下麵簡介,官方下載步驟:

  1. 點擊鏈接 a number of third party vendors.:

image

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

image

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

image

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

image

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

image

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

image

  1. 以管理員身份運行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服務

image

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

image

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

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

image

接下來進入到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這個網址,就應該出現如圖:

image

準備工作到此結束,接下來就讓我們在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 = 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)

image

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

image

此時的報錯原因是,網路安全策略不允許:

為保證用戶數據和設備的安全,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中的列印日誌,如圖:

image

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 = 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中的列印日誌,你會看到和上面一樣的結果。

image

除了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這個網址,就應該出現如圖:

image

把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 = 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按鈕,結果如圖:

image

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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 上一篇文章我們演示瞭如何《在 S3 備份恢復 RadonDB MySQL 集群數據》,本文將演示在 KubeSphere[1] 中使用 Prometheus[2] + Grafana[3] 構建 MySQL 監控平臺,開啟所需監控指標。 背景 Prometheus 基於文本的暴露格式,已經成為雲原生 ...
  • 聲明:全文來源《mysql SQL必知必會(第3版)》 第一章 瞭解SQL 1.1 資料庫基礎 資料庫(database)保存有組織的數據的容器 表(table)某種特定類型數據的結構化清單。資料庫中的每個表都有一個用來標識自己的名字。此名字是唯一的。 模式(schema)關於資料庫和表的佈局及特性 ...
  • 本文介紹什麼是通配符、如何使用通配符,以及怎樣使用 SQL LIKE 操作符進行通配搜索,以便對數據進行複雜過濾。 一、LIKE 操作符 前面介紹的所有操作符都是針對已知值進行過濾的。不管是匹配一個值還是多個值,檢驗大於還是小於已知值,或者檢查某個範圍的值,其共同點是過濾中使用的值都是已知的。 但是 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
  • 本文和接下來的幾篇文章為閱讀郭霖先生所著《第一行代碼:Android(篇第2版)》的學習筆記,按照書中的內容順序進行記錄,書中的Demo本人全部都做過了。 每一章節本人都做了詳細的記錄,以下是我學習記錄(包含大量書中內容的整理和自己在學習中遇到的各種bug及解決方案),方便以後閱讀和查閱。最後,非常 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...