我想大家應該也厭倦一直講 View
的部份了吧,雖然說身為 App 工程師 UI 是非常重要的但的確有其他面向也必須學習,今天我們來提一下怎麼在手機上優雅的做連線取得資料吧。
首先很重要的是必須要在 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 也被稱為 UI Thread,是 Android 提供的預設 Thread
。
Android 所有 components 諸如 Activity
、Service
都是跑在 Main Thread 上,所有 View
的操作也都必須執行在 Main Thread 上,相對的任何有關網路的連線,都不能跑在 Main Thread 上。
更多資料請參考:
https://developer.android.com/guide/components/processes-and-threads
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 有以下幾種:
大家也可以在以下這個網址看是否有你需要的 converter。
https://mvnrepository.com/artifact/com.squareup.retrofit2
我們一起來看個例子吧,假設我們要連 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