1. 程式人生 > 實用技巧 >《第一行程式碼》閱讀筆記(二十八)——網路技術(OkHttp+JSON/GSON)

《第一行程式碼》閱讀筆記(二十八)——網路技術(OkHttp+JSON/GSON)

網路技術在程式設計中也是非常重要的一環,在android底層是通過HttpURLConnection實現的,後來出現了一款優秀的框架OkHttp,實現了對底層的封裝。然後隨著技術的進步,現在更多的是使用OkHttp+Retrofit+Rxjava網路框架。這裡書中沒有詳細說,後面筆者會對這些部分進行一個補充。

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:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

第二步:修改MainActivity

package com.firstcode.webviewtest;

import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        WebView webView = findViewById(R.id.web_view);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient());
        webView.loadUrl("https://www.baidu.com/");
    }
}

非常的簡單,就讓我們來看下作者是怎麼解釋的吧

——第一行程式碼
MainActivity中的程式碼也很短,首先使用findViewById( )方法獲取到了WebView的例項,然後呼叫WebView的getSettings()方法可以去設定一些瀏覽器的屬性,這裡我們並不去設定過多的屬性,只是呼叫了setJavaScriptEnabled ()方法來讓WebView支援JavaScript 指令碼。
接下來是非常重要的一個部分,我們呼叫了WebView 的setWebViewClient()方法,並傳人了一個WebViewClient的例項。這段程式碼的作用是,當需要從一個網頁跳轉到另一個網頁時,我們希望目標網頁仍然在當前WebView中顯示,而不是開啟系統瀏覽器。
最後一步就非常簡單了,呼叫WebView的loadUrl()方法,並將網址傳入,即可展示相應網頁的內容,這裡就讓我們看一看百度的首頁長什麼樣吧。

第三步:設定許可權
在AndroidManifest.xml中加入下面這句語句即可

這個非常重要,經常容易忘記。

HttpURLConnection

通過WebView打開了網路世界的大門,之後我們就可以使用網路技術了。這就提到了一個HttpURLConnection,讓我們看看是如何實現的吧。這個並不是很重要,因為後面要講的OkHttp才是關鍵。但是底層的基礎也不能完全不瞭解,所以大家還是需要看看。

第一步:新建專案,修改主頁佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/send_request"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send Request" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/response_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </ScrollView>

</LinearLayout>

非常簡單,不多說。

第二步:修改主活動程式碼

package com.firstcode.networktest;

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;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    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(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                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(100000);
                    connection.setReadTimeout(100000);
                    InputStream in = connection.getInputStream();

                    reader = new BufferedReader(new InputStreamReader(in));
                    StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        response.append(line);
                    }
                    showResponse(response.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }

    private void showResponse(final String response) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                responseText.setText(response);
            }
        });
    }
}

筆者在這裡做了一些修改,沒有抽取點選事件了,怎麼方便怎麼來。其實大家也應該怎麼做都會做,做到想放哪裡就放哪裡就OK了。

這裡主要說一下這個runOnUiThread() 方法,使用它是因為Android是不允許在子執行緒中進行UI操作的,我們需要通過這個方法將執行緒切換到主執行緒,然後再更新UI元素。

注意事項

如果是android版本比較高的手機用來做測試只能使用https的請求,http的請求無法顯示,想讓http的請求顯示需要以下設定。

  1. 新建xml資料夾,並建立xml檔案,如下圖所示

  1. 輸入以下內容
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
  1. 最後在application標籤中,加入android:networkSecurityConfig="@xml/config"

post請求

輸入以下屬性即可

connection.setRequestMethod(" POST");
                    DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                    out.writeBytes("username=admin&password=123456");

OkHttp的簡單使用

官網資訊

OkHttp官網
Get a URL
This program downloads a URL and prints its contents as a string. Full source.
這段編碼下載一個URL並將其內容列印為字串。更多原始碼

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

Post to a Server
This program posts data to a service. Full source.
這段編碼提交一個data資料到伺服器。更多原始碼

public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

Further examples are on the OkHttp Recipes page.
更多的例子在這裡。

看不懂?沒關係,看看郭神怎麼說。

Get

第一步:匯入依賴
implementation("com.squareup.okhttp3:okhttp:3.4.1")
更新了一下匯入依賴的方式,但是版本保持和作者的一樣。

第二步:修改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://m.iyuba.cn/jlpt1/getConceptWordOver.jsp?app=primary")
                            .build();
                    Response response = client.newCall(request).execute();
                    String responseData = response.body().string();
                    showResponse(responseData);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

首先需要建立一個OkHttpClient例項。使用Request request = new Request.Builder().build();建立一個request的空物件,再通過鏈式程式設計.url()來獲取介面。使用newCall回撥一個call,再通過.execute()傳送請求,並接收資料。最後通過response.body().string();獲取內容。因為獲取網路請求的執行緒是不能修改UI的所有需要編寫一個方法來重新整理UI,在這裡就是showResponse(responseData);,它也是一個執行緒。

Post

post請求比get請求多一個RequestBody,具體如下:

OkHttpClient client = new OkHttpClient();
                    RequestBody requestBody = new FormBody.Builder()
                            .add("username", "admin")
                            .add("password", "123456")
                            .build();                    
                    //需要可以接收RequestBody的url
                    Request request = new Request.Builder()
                            .url("***")
                            .post(requestBody)
                            .build();
                    Response response = client.newCall(request).execute();
                    String responseData = response.body().string();

JSONObject

現在網路上都使用JSON作為前後端互動的基礎。如何使用JSON?
第一步:新增方法parseJSONWithJSONObject,傳入responseData

  parseJSONWithJSONObject(responseData);
              showResponse(responseData);//

第二步:編寫方法

 private void parseJSONWithJSONObject(String jsonData) {
        try {
            JSONArray jsonArray = new JSONArray(jsonData);
            for (int i = 0; i < 5; i++) {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                String voaId = jsonObject.getString("voa_id");
                String word = jsonObject.getString("word");
                Log.i(TAG, "id is" + voaId);
                Log.i(TAG, "word id " + word);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

JSONArray可以例項化並接收json字串,然後使用jsonArray.getJSONObject(i)獲取每一個物件賦值給JSONObject,通過getString方法獲取內容。非常好理解。

Gson

本章節沒有使用書中那麼簡單的案例,正好實習公司有一段需求,拿來練練手,順便記錄下。

第一步:匯入依賴
implementation 'com.google.code.gson:gson:2.8.6'

第二步:安裝外掛
書中也提到了需要為Gson資料匹配實體類,但是那太麻煩了。直接自動生成。外掛名字是GsonFormat

新建一個類,自動生成相應資料。步驟如下:

沒什麼難度。

第三步:修改

因為筆者自己的連結,json資料有個巢狀,這個在公司中也是非常常見的。就像是資料庫的一對多關係。有個size屬性,和data屬性,data中就是資料。如圖

如果使用書上的方法,會報錯。
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 11 column 2 path $

所以我們再建一個類,進行抽取。很巧妙吧

package com.firstcode.okhttptest;

import java.util.List;

public class Temp {
    private String size;
    private List<Vocabulary> data;

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    public List<Vocabulary> getData() {
        return data;
    }

    public void setData(List<Vocabulary> data) {
        this.data = data;
    }
}

其實只要把所有的json全部放到gsonformat的文字框裡面就行,但是資料太多,電腦太爛,一執行就卡死。無奈提升了自己水平。

第四步:修改parseJSONWithGson

private void parseJSONWithGson(String jsonData) {
        Gson gson = new Gson();
        Temp temp = gson.fromJson(jsonData, Temp.class);
        List<Vocabulary> vocabularyList = temp.getData();
        for (Vocabulary v : vocabularyList) {
            Log.i(TAG, "word is" + v.getWord());
        }
    }

結果

書中還介紹了一種方法,值得注意。

——第一行程式碼
如果需要解析的是一段JSON陣列會稍微麻煩一點,我們需要藉助TypeToken將期望解析成的資料型別傳入到fromJson()方法中,如下所示:
List people = gson.fromJson(jsonData, new TypeToken<List>() {}. getType());

網路程式設計的最佳例項

HttpURLConnection的就不寫了,大家有興趣的看下。

OkHttp的主要實現方法就是

package com.iyuba.primaryenglish.util;

import okhttp3.OkHttpClient;
import okhttp3.Request;

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);
    }
}

使用時,編寫如下

HttpUtil.sendOkHttpRequest("http://www.baidu.com", new okhttp3.Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //在這裡對異常情況進行處理
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //得到伺服器返回的具體資訊
                String reponseData = response.body().string();
            }
        });