iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 12
0
Mobile Development

Android 十全大補系列 第 12

[Android 十全大補] Retrofit

我想大家應該也厭倦一直講 View 的部份了吧,雖然說身為 App 工程師 UI 是非常重要的但的確有其他面向也必須學習,今天我們來提一下怎麼在手機上優雅的做連線取得資料吧。

Permission

首先很重要的是必須要在 AndroidManifest.xml 裡加上 uses-permission 來宣告我們需要存取網路的權限如下:

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

當我們有 GPS、檔案存取、拍照錄音等比較跟隱私有關的需求的話,也會需要列出。Android 6.0 之後使用者也可以拒絕你的這類需求,如何處理的話請看文件:
https://developer.android.com/training/permissions/requesting

Main Thread vs Others

Main Thread 也被稱為 UI Thread,是 Android 提供的預設 Thread
Android 所有 components 諸如 ActivityService 都是跑在 Main Thread 上,所有 View 的操作也都必須執行在 Main Thread 上,相對的任何有關網路的連線,都不能跑在 Main Thread 上。

更多資料請參考:
https://developer.android.com/guide/components/processes-and-threads

Retrofit

Retrofit 是由 square 這家有名公司的 open source project。主要用途是用簡單的 interface 來表示 api 的連線。

Square 裡有很厲害的 Android 大神 JakeWharton 大家可以 follow 一下喔:
https://github.com/JakeWharton

首先要加上 Retrofit 的 dependency 到 build.gradle 裡。

dependencies {
  implementation 'com.squareup.retrofit2:retrofit:2.6.2'
  implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
}

com.squareup.retrofit2:retrofit 是 Retrofit 的主要程式,而 converter-gson 則是指名我們要用 gson 的方式把 api 的結果轉成我們的物件。常見的 converter 有以下幾種:

  • Gson: com.squareup.retrofit2:converter-gson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf

大家也可以在以下這個網址看是否有你需要的 converter。
https://mvnrepository.com/artifact/com.squareup.retrofit2

Example

我們一起來看個例子吧,假設我們要連 Github 的 api 拿到某個人的 repo 列表,

GET /users/:username/repos

Parameter

Name Type Description
type string Can be one of all, owner, member. Default: owner
sort string Can be one of created, updated, pushed, full_name. Default: full_name

如果對 Github api 有興趣的話:
https://developer.github.com/v3/repos/#list-user-repositories

回傳的範例長這樣,(我們省略掉很多欄位方便顯示,如果想看完整的範例可以連到 https://api.github.com/users/Jintin/repos 看看,或把 Jintin 改成任何人的 github name):

[
  {
    "id": 46051130,
    "name": "Swimat",
    "private": false,
    "owner": {
      "login": "Jintin",
      "id": 3413874,
      "avatar_url": "https://avatars3.githubusercontent.com/u/3413874?v=4"
    },
    "html_url": "https://github.com/Jintin/Swimat",
  },
]

我們可以依據這個 json 的格式定義我們 kotlin 的 data class:

data class Repo(val id: Int, val name: String, val owner: User)

data class User(val id: Int, @SerializedName("avatar_url") val avatarUrl: String)

SerializedName 是 gson 的 annotation 用來做 json object 跟 java/kotlin object 的 mapping 依據。
如果不清楚 data class 的好處可以參考官方文件:
https://kotlinlang.org/docs/reference/data-classes.html

使用 Retrofit 表示則會長像這樣子:

interface GitHubService {
    @GET("users/{user}/repos")
    fun listRepos(
        @Path("user") user: String,
        @Query("type") type: String? = null,
        @Query("sort") sort: String? = null
    ): Call<List<Repo>>
}

@GET 代表著 http method,annotation 裡面的 string 是 url pattern,url 用 {} 包起來的部分是變數,會對應到 function 裡面的 @Path 一樣名字的參數,@Query 就是 url 的 query string,因為這些參數可以不帶我們把它改成 optional 而且預設為 null,最後 function 的回傳就是我們 api 正常回傳的結果,非常清楚的就完整表達了一隻 api 的所有意圖,而且不含任何程式碼,真正的程式碼會由 dynamic dispatch 動態處理,是不是非常的優雅呢?

那我們怎麼拿到 GitHubService 實體呢?先透過 Retrofit.Builder 建立一個 retrofit 實體,然後把我們 api 的 class 傳給 retrofit 就可以囉,範例如下:

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(GsonConverterFactory.create())
    .build()
val service = retrofit.create(GitHubService::class.java)

我們的 service 物件就可以直接呼叫 listRepos 拿到我們的 List<Repo> 囉?

代誌當然不是憨人想的那麼簡單,我們剛剛有提到 Android 的 api 連線必須在 background thread,而且 View 操作必須再回到 ui thread,但 Retrofit 也有提供同步與非同步二種方法,當我們在 ui thread 的時候可以使用 enqueue 這個 function 讓結果從 callback function 非同步回來,如果你已經在 background thread 或你不是寫 Android app 的話可以直接用 execute 就可以拿到結果囉。
非同步的範例程式如下:

service.listRepos("Jintin")
    .enqueue(object : Callback<List<Repo>> {
        override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
            // network fail
        }

        override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
            if (response.isSuccessful) {
                // success
            } else {
                // application level fail
            }
        }

    })

除了在 Call<?> 這種回傳結構,Retrofit 也支援 Observable 的方式,有機會的話將來我們介紹 RxJava 也會提到喔!!

以上就是今天的內容了,歡迎進入 multi thread 的平行時空。

Android 十全大補已經正式出書上架囉!
有興趣的讀者歡迎參考:
https://www.tenlong.com.tw/products/9789864345786


上一篇
[Android 十全大補] Fragment
下一篇
[Android 十全大補] Annotation
系列文
Android 十全大補30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言