iT邦幫忙

2023 iThome 鐵人賽

DAY 6
0
Kotlin

喝咖啡要30天?一起用 Kotlin 打造尋找好喝咖啡的 App系列 第 6

Day6 使用 OKHttp 串接全台咖啡廳資料的 API - 3 | 非同步執行 - Callback

  • 分享至 

  • xImage
  •  

Day6 使用 OKHttp 串接全台咖啡廳資料的 API-3 | 非同步執行 - Callback

昨天學到了如何同步執行向 server 取得全台咖啡廳資料,來複習一下,同步是程式碼會按照我們撰寫的順序依序執行,在閱讀上是十分直觀的。那大家可以猜到非同步執行了嗎~ 沒錯,非同步表示程式碼並不會按照順序跑,在閱讀上相對不容易。那在什麼情況下會需要使用到非同步執行呢? 讓我們來想像若生活上沒有非同步執行會發生什麼事~

沒有非同步,人生都在等待就飽了

同步執行 (Synchronous) 的全聯

我是全聯的店員,整間店只有我一位,需要負責補貨、拉排面、整理環境和結帳等等。在執行結帳的時候,原本只有2~3位客人,突然結帳的隊伍暴增,但因為只有我一位店員,只能繼續努力的結帳。阿,送貨的司機來了,可是還沒辦法收貨,只能請司機加入等待的隊伍,等我將這排長長的結帳隊伍處理完畢,才能執行收貨。

非同步執行 (Asynchronous) 的全聯

接著來看看使用非同步的全聯會如何運作。

我是全聯的店員,但這間店的員工除了我之外還有A 同事和 B 同事,好開心。今天幫客人結帳的時候突然排隊的人暴增,但沒關係,只要呼叫 “請支援收銀”,我的 AB 同事就會過來幫忙,他們幫忙結完帳後還會通知我結束了,好開心。

看完狀況劇大家有比較理解在程式上的兩種執行方式嗎? 當主執行緒在執行非同步任務時,會請其他執行緒幫忙,在等待期間,主執行緒可以繼續去做原本在做的,或是去處理其他的事情,等非同步的執行結果出來後,幫忙的執行緒會將執行結果通知給主執行緒,告訴它我做完了。常用於執行較耗時的操作例如上傳下載,或是不曉得什麼時候可以取得執行結果像是和 server 請求資料時,都可以透過非同步執行做處理,在使用者體驗上也會比較好,用戶就不需要為了等待執行結果而卡住不能做其他事。

資料來源

資料來源

非同步執行 - 建立新的執行緒

為了不讓主執行緒在執行耗時的任務阻塞,那我們來試著建立新的執行緒處理取得咖啡廳的資料,就像是高速公路因為上下班導致交流道壅塞,而加開交流道是一樣的道理。

在 Kotlin 我們可以透過 thread{} 建立執行緒來請他幫忙做事,但 OKHttp 它有自己寫好的非同步呼叫,只要將原本的 execute() 改為 enqueue() 即可。

💡 情境 : 點擊按鈕 → 使用非同步執行取得咖啡廳資料 → callback 接收到回傳結果 → 在 UiThread 內刷新畫面。

這是目前的 getCoffeeShops() :

private fun getCoffeeShops() {

    // 創建一個單線程執行緒池
    val service = Executors.newSingleThreadExecutor()

    // 提交任務到執行緒池
    service.submit {

        // 創建一個 OkHttpClient 實例
        val client = OkHttpClient()

        // 設置要發送的 HTTP 請求
        val request = Request.Builder()
            .url("http://cafenomad.tw/api/v1.2/cafes/taipei")
            .build()

        // 在主線程中執行 OKHttp 請求(請注意這是同步操作,會阻塞主線程)
        try {
            // 使用 OkHttpClient 發送同步請求
            val response = client.newCall(request).execute()

            // 檢查回應是否成功
            if (response.isSuccessful) {

                val responseBody = response.body?.string()

                // 在主線程更新 UI,顯示回應內容
                runOnUiThread {
                    binding.textView.text = responseBody
                }
            }
            else {
                // 處理請求失敗的情況
                println("Request failed with code: ${response.code}")
            }

            // 關閉執行緒池
            service.shutdown()
        }
        catch (e: Exception) {

            e.printStackTrace()
        }
    }
}

修改 getCoffeeShops() 方法,並使用 OKHttp 的非同步執行,且透過 Callback 取得咖啡廳資料,最後顯示在 TextView

  1. 將原先的 getCoffeeShops() 使用 Lamda 的方式加入參數 Callback

    private fun getCoffeeShopsAsync(callback: (String?) -> Unit) {
    
    }
    

    這段程式碼的意思是我有一個 getCoffeeShopsAsync() 方法,方法有一個參數 callback,他吃的參數型別為 String?Unit 表示沒有回傳值。

  2. 將 OKHttp 改用非同步執行的 enqueue()

    // 使用 OkHttpClient 發送非同步請求
    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
    
            e.printStackTrace()
            callback.invoke(e.message)
        }
    
        override fun onResponse(call: Call, response: Response) {
    
            if(!response.isSuccessful) {
    
                callback.invoke("fail! response code = ${response.code} \nmessage = ${response.message}")
            }
            else {
    
                val responseBody = response.body?.string()
                callback.invoke(responseBody)
            }
        }
    })
    

    這邊的 enqueue() 傳入了一個 Callback,並且覆寫兩個方法: onFailure()onResponse()

    我們在 onFailure() 處理呼叫 API 失敗的狀況;在 onResponse() 接收回傳的資料,接著透過從方法傳入的 callback 將結果拋出去。

  3. 刷新畫面的 TextView

    // 取得咖啡廳資料
    binding.button.setOnClickListener {
    
        getCoffeeShopsAsync { shops ->
            runOnUiThread {
    
                binding.textView.text = shops
            }
        }
    }
    

    要注意畫面的刷新必須在 runOnUiThread {} 內處理,因為我們在呼叫 API 時已經切換到別條的執行緒了,若沒有在 UiThread 更新畫面肯定會報錯~~~

原本以為今天可以讀到 Coroutine,結果太高估自己了呀~~~ 只好明天見拉各位 ❤️

今日碎念

累到什麼都唸不出來….明天見拉大家~~

13 年前的歌,歲月阿….
Yes


上一篇
Day5 使用 OKHttp 串接全台咖啡廳資料的 API - 2
下一篇
Day 7 使用 OKHttp 串接全台咖啡廳資料的 API-4 | 非同步執行與 Coroutine
系列文
喝咖啡要30天?一起用 Kotlin 打造尋找好喝咖啡的 App30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言