iT邦幫忙

2021 iThome 鐵人賽

DAY 8
1
Mobile Development

Android 口罩地圖入門實戰 30 天 (使用 Kotlin 程式語言)系列 第 8

Day 8:OkHttp 獲取網路資料方式

本篇文章同步發表在 HKT 線上教室 部落格,線上影音教學課程已上架至 UdemyYoutube 頻道。另外,想追蹤更多相關技術資訊,歡迎到 臉書粉絲專頁 按讚追蹤喔~

程式碼範例

範例名稱:OkHttp 基本拉資料方式 (GET 請求範例)
開發人員:HKT (侯光燦)
程式語言:Kotlin
開發環境:Android Studio 4.1.1 & Android 11 & Kotlin 1.4.21
授權範圍:使用時必須註明出處且不得為商業目的之使用
範例下載點:點我下載

在 Part 1 介紹當中,我們簡單介紹完 Kotlin 基礎語法與口罩開發背景與環境之後,接下來幾天,將要來跟大家介紹網路資料處理的部分,如何將政府公開的口罩庫存資料,從雲端伺服器,下載到本地手機裝置上。然後進一步去了解,資料下載回來,口罩 Json 資料格式,如何去做使用與處理,大致上規劃,如下:

  • Day 8:OkHttp 獲取網路資料方式
  • Day 9:JSON 資料解析
  • Day 10:Gson 資料解析
  • Day 11:ProgressBar 忙碌圈圈
  • Day 12:封裝 OkHttp

Android 業界常用獲取網路資料方式,有 OkHttp、Retrofit、Volley 和 HttpURLConnection 四種連線方式。這四種寫法其實很類似,大同小異,所以今天 KT 就選擇業界最多人使用的 OkHttp 連線方式來跟大家介紹,想看其他使用方式,可以參考:

Android Retrofit 教學 (Java 篇)
https://tw-hkt.blogspot.com/2020/03/retrofit-java.html

Android Volley 教學 (Kotlin 篇)
https://tw-hkt.blogspot.com/2020/07/android-volley-kotlin.html

Fuel (另外推薦,很簡潔的連線方式)
https://github.com/kittinunf/fuel

添加 OkHttp 依賴庫 (dependencies)

要使用 OkHttp,必須額外宣告,在 GRADLE (Module) 層級的 dependencies 中內加入:

implementation 'com.squareup.okhttp3:okhttp:4.9.0'

若想使用最新版本可以到 MVN REPOSITORY 查看最新版號:

目前 Android Studio 很強大,若有新版本,會在你宣告的當下,該條項目反灰呈現,鼠標移動過去,即可以立即查看到當下最新版號。

權限宣告 (permissions)

連線網路,需在 AndroidManifest.xml 中宣告:

<uses-permission android:name="android.permission.INTERNET" />

OkHttp 使用介紹

OkHttp 主要可以分成三個部分:

Part 1. OkHttpClient

設定連線基底(SSL、TLS、連線憑證)。

Part 2. Request

設定 URL 連線網址、請求方式(GET、POST、PUT和DELETE方法)、Header 資訊

Part 3. Call

設定 execute 同步(Synchronous)或 enqueue 非同步(Asynchronous),執行連線後,可獲取到回應的結果資料。

OkHttp 基本拉資料方式 (GET 請求範例)

private fun getPharmacyData() {
    //口罩資料網址
    val pharmaciesDataUrl =
            "https://raw.githubusercontent.com/thishkt/pharmacies/master/data/info.json"

    //Part 1: 宣告 OkHttpClient
    val okHttpClient = OkHttpClient().newBuilder().build()

    //Part 2: 宣告 Request,要求要連到指定網址
    val request: Request = Request.Builder().url(pharmaciesDataUrl).get().build()

    //Part 3: 宣告 Call
    val call = okHttpClient.newCall(request)

    //執行 Call 連線後,採用 enqueue 非同步方式,獲取到回應的結果資料
    call.enqueue(object : Callback {
        override fun onFailure(call: Call, e: java.io.IOException) {
            Log.d("HKT", "onFailure: $e")
        }

        override fun onResponse(call: Call, response: Response) {
            Log.d("HKT", "onResponse: ${response.body?.string()}")
        }
    })
}

輸出結果

{
  "type": "FeatureCollection",
  "features": [
   ...
   ...
   ...,{
            "type": "Feature",
            "properties": {
                "id": "5901024427",
                "name": "博昱仁愛藥局",
                "phone": "(02)87739258",
                "address": "臺北市大安區仁愛路4段65號",
                "mask_adult": 0,
                "mask_child": 450,
                "updated": "2020\/09\/13 11:32:37",
                "available": "星期一上午看診、星期二上午看診、星期三上午看診、星期四上午看診、星期五上午看診、星期六上午看診、星期日上午看診、星期一下午看診、星期二下午看診、星期三下午看診、星期四下午看診、星期五下午看診、星期六下午看診、星期日下午看診、星期一晚上看診、星期二晚上看診、星期三晚上看診、星期四晚上看診、星期五晚上看診、星期六晚上看診、星期日晚上看診",
                "note": "週間(週一至週五)上午9點發放號碼牌收取健保卡,下午2點領取",
                "custom_note": "",
                "website": "",
                "county": "臺北市",
                "town": "大安區",
                "cunli": "仁愛里",
                "service_periods": "NNNNNNNNNNNNNNNNNNNNN"
            },
            "geometry": {
                "type": "Point",
                "coordinates": [
                    121.546869,
                    25.038194
                ]
            }
        },
    ...
    ...
    ...
    
  ]
}

POST 請求範例

口罩資料,沒有 POST 功能,這裡 KT 另外選用 Reqres 網站,來練習 Post 如何使用。Reqres 是一個提供假資料 API 串接的網站,方便練習 RESTful API。

val testUrl =
    "https://reqres.in/api/users"

//Part 1: 宣告 OkHttpClient
val okHttpClient = OkHttpClient().newBuilder().build()

//加入 FormBody 參數 name 和 job 。
val formBody: FormBody = FormBody.Builder()
    .add("name", "HKT")
    .add("job", "Teacher")
    .build()
    
//Part 2: 宣告 Request,要求要連到指定網址
val request: Request = Request.Builder().url(testUrl).post(formBody).build()

//Part 3: 宣告 Call
val call = okHttpClient.newCall(request)

//執行 Call 連線後,採用 enqueue 非同步方式,獲取到回應的結果資料
call.enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        e.printStackTrace()
    }

    override fun onResponse(call: Call, response: Response) {
        Log.d("QQQ", "response: ${response.body?.string()}")
    }
})

輸出結果

{
  "name": "HKTTTT",
  "job": "Teacher",
  "id": "457",
  "createdAt": "2020-09-15T09:10:37.252Z"
}

Log 日誌連線資訊

OkHttp 提供的 HttpLoggingInterceptor,加入後,Log 會自動印出連線的網址、回應狀態碼和連線回應的花費的時間。

需額外添加 interceptor 依賴庫 (dependencies),在 GRADLE (Module) 層級的 dependencies 中內加入:

implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'

interceptor 版號要跟 okhttp 匹配,否則會發生不可預期的錯誤

在 okHttpClient 加入連線攔截資訊

private val okHttpClient = OkHttpClient().newBuilder().addInterceptor(
    HttpLoggingInterceptor().setLevel(
        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor.Level.BASIC
        } else {
            HttpLoggingInterceptor.Level.NONE
        }
    )
).build()

OkHttp 官方介紹與使用教學

OkHttp 更多詳細使用方式,可以參考官網介紹:https://square.github.io/okhttp/

常見錯誤

Q1

Trying to parse JSON data from URL : Unable to resolve host “jsonplaceholder.typicode.com”: No address associated with hostname

解答:
若發生以上類似錯誤,有可能你忘記在 AndroidManifest 宣告,使用網路權限,另外一個就是你的網路不通。

Q2

okhttp3.internal.http.RealResponseBody@2f58b42

解答:
若印出來的結果資料,類似上方文字,需改取出 body 內容資料,並加上 ? 問號

${response.body?.string()}

Q3

E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.example.test, PID: 20263
java.lang.NoSuchMethodError: No virtual method log(ILjava/lang/String;Ljava/lang/Throwable;)V in class Lokhttp3/internal/platform/Platform; or its super classes (declaration of 'okhttp3.internal.platform.Platform' appears in /data/app/com.example.test-x8l0H_ZbvI-Dn6Lugsdl6Q==/base.apk)
at okhttp3.logging.HttpLoggingInterceptor$Logger$1.log(HttpLoggingInterceptor.java:110)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:160)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:517)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)

解答:
okhttp 與 interceptor 依賴版本(dependencies) 兩個版號不一致所導致。例如 4.9.0,兩者的版號,應該都是 4.9.0,否則會發生類似以上錯誤。

Q4

FATAL EXCEPTION: OkHttp Dispatcher

解答:
Response 只能用一次,你可能使用了兩次。應該將資料存到變數中,再去做其他使用。更進一步解釋,可參考 OkHttp 官方文件說明:ResponseBody

參考資料

HKT 線上教室
https://tw-hkt.blogspot.com/

Freepik
https://www.freepik.com/

OkHttp
https://square.github.io/okhttp/


那今天【iThome 鐵人賽】就介紹到這邊囉~

順帶一提,KT 線上教室,臉書粉絲團,會不定期發佈相關資訊,不想錯過最新資訊,不要忘記來按讚,追蹤喔!也歡迎大家將這篇文章分享給更多人喔。

我們明天再見囉!!!掰掰~


上一篇
Day 7:git 版本控制
下一篇
Day 9:JSON 資料解析
系列文
Android 口罩地圖入門實戰 30 天 (使用 Kotlin 程式語言)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言