Retrofit 是 Android 和 Java 中 處理 HTTP 請求的 Thired party library ,他是基於 OKHttp 的 Restful HTTP Network 框架,並且支持 Gson
、 Json
、 Xml
等序列化數據 Encode
與 Decode
Android App->Retrofit: 發起 Request
Note right of Retrofit: 封裝 Message 、Header 、Url 等訊息成封包
Retrofit -> OkHttp: 封包交給 OkHttp
OkHttp -> Server: 將封包透過 get 或 Post 丟給 Server
Server -> OkHttp: Response 數據封包
OkHttp -> Retrofit: 交給 Retrofit
Note right of Retrofit: 解析封包,並回原程序列化數據
Retrofit -> Android App: 返回序列化數據
dependencies
中將 Retrofit2
和 OkHttp3
加到 app/build.gradle
的 dependencies
區塊中
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// OkHttp3
implementation 'com.squareup.okhttp3:okhttp:3.12.1'
Internet
權限到 <uses-permission>
中Android 因為資安的原因,因此會將一些功能不加入預設的 App 中,而是當開發者有需要時,開發者要自己手動加入其中,而開發者就是透過 <uses-permission>
向 OS 要求要哪些 Service
,那 <uses-permission>
會寫在 AndroidManifest.xml
之中,而有些更私密的權限請求,甚至需要使用者同意, App 才能夠使用,例如: 訪問外部儲存空間( External Storage )
不同版本的 Android ,需要要求的 Service 也不盡相同,因此可以到下面的網址去看看目前服務的把本有哪些是需要要求服務權限的
那網路的 Service 也是需要和 Android OS 要求的,如果沒有加上去的話, App 執行時就沒辦法打 Request 出去,因此需要把
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
也加到 AndroidManifest
之中
那麼我們首要工作就是要用 Retrofit 實作下面兩個 Url ,讓 Retrofit 可以丟請求到這個個 Url 去
我們可以先到這兩個 Url 中,看看裏面的資料格式大概長怎樣,以及我們需要取出哪些資料格式
接下來的都會先以第 1 個 Url : https://pokeapi.co/api/v2/pokemon?offset=0&limit=10 當作例子
那因為回傳資料都是 Json ,因此我們會需要有工具幫助我們從中解析 Key-Value ,那要解析 Json 很直覺的就會想到 Gson ,但這次鐵人賽就是要玩玩看新東西,因此我決定嘗試 Moshi ,另一款 Json 的 encode 、decode 框架,根據一些人的評測,效能上比 Gson
更佳,並且能力也不會比 Gson
遜色
因此我們這邊一樣要將 Moshi
加到 app/build.gradle
的 dependencies
區塊中
// moshi
implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.9.2'
因為有用到 kapt
,因此要將 kapt
加到最上面的 plugin
apply plugin: 'kotlin-kapt'
那我們就可以建立之前有講解過的 data class
搭配 moshi
去解析回傳資料和將資料以結構化儲存起來
因此在 data/model
下,先建立 Priate.kt
和 PirateResponse.kt
,兩者都是 data class
@JsonClass(generateAdapter = true)
data class PirateResponse(
@field:Json(name = "count") val count: Int,
@field:Json(name = "next") val next: String?,
@field:Json(name = "previous") val previous: String?,
@field:Json(name = "results") val results: List<Pirate>
)
@JsonClass(generateAdapter = true)
data class Pirate(
@field:Json(name = "name") val name: String,
@field:Json(name = "url") val url: String
)
可以注意到 PirateResponse
中有用到 Pirate
,這兩個 data class
是有關聯性的,會這樣設計的原因是因為 https://pokeapi.co/api/v2/pokemon?offset=0&limit=10 回傳 Json 格式長這樣
建立完 data class
後就要開始設計接口,正常 Url 會具備 scheme://host+path?query
這樣的格式
以 https://pokeapi.co/api/v2/pokemon?offset=0&limit=10
為例
scheme -> https
host -> pokeapi.co
path -> api/v2/pokemon
query -> offset=0&limit=10
Retrofit
採用註解方式去描述 HTTP 的請求,例如常見的 @Get
、@Post
或 @Put
等等,那我們這次打的都是 @GET
,將上面的 Url 轉換到 Retrofit ,就會長的像這樣
@GET("pokemon")
fun fetchPirateList(
@Query("limit") limit: Int = 10,
@Query("offset") offset: Int = 0
): Call<PirateResponse>
這邊可以注意到 function
的 return
值是 Call<PirateResponse>
,其中 Call 是負責傳輸資料到 Server 和接收 Server 回傳的資料,透過他就可以取得 Url 的資料,並且 Call 的泛型是帶 PirateResponse
,因此回傳資料會被放到 PirateResponse data class
中
上面的 function 會寫在 PokedexService.kt
中,這是一個在 data/api
資料夾下新建出來的 Interface
完成Restful API 接口後,接下來就是實作 Retrofit 和 OkHttp Client
首先先到 utils/
下面新建 NetworkManager.kt
並且他是一個 object
,裡面會再有兩個 Function ,分別是 provideOkHttpClient
和 provideRetrofit
addInterceptor
,除了這個方法也有 addNetworkInterceptor
,具體差別可以參考 Okhttp-wiki 之 Interceptors 拦截器
Moshi
,因此這邊的 addConverterFactory
就會放 MoshiConverterFactory.create()
最後我在 MainActivity
上面放了一個按鈕
然後幫按鈕寫了一個簡單的 onClickListener
事件
fetch_data.setOnClickListener {
val apiService = NetworkManager.provideRetrofit(NetworkManager.provideOkHttpClient())
.create(PirateService::class.java)
apiService.fetchPirateList().enqueue(object : Callback<PirateResponse> {
override fun onResponse(
call: Call<PirateResponse>,
response: Response<PirateResponse>
) {
Log.d(TAG, "response: ${response.body().toString()}")
}
override fun onFailure(call: Call<PirateResponse>, t: Throwable) {
Log.d(TAG, "error: ${t.message}" ?: "Get some error")
}
})
}
provideRetrofit()
並且把 provideOkHttpClient()
傳進去做封裝,而 .create()
裏面就是塞入剛剛定義的 Restful API 接口介面fetchPirateList()
取得 API Response ,並採用 enqueue function
進行異步操作onResponse()
,在這邊我會把 PirateResponse
的內容印出來。但如果失敗的話,會進到 onFailure()
,而這邊就會印出 Error message全部程式碼會長的像這樣,完成之後就可以把 App 執行起來
那按下按鈕後可以在 Logcat
中看到成功把資料取回來了,大功告成!
2020-09-16 02:42:19.313 26970-26970/com.example.piratehegemony D/Companion: response: PirateResponse(count=1050, next=https://pokeapi.co/api/v2/pokemon?offset=10&limit=10, previous=null, results=[Pirate(name=bulbasaur, url=https://pokeapi.co/api/v2/pokemon/1/), Pirate(name=ivysaur, url=https://pokeapi.co/api/v2/pokemon/2/), Pirate(name=venusaur, url=https://pokeapi.co/api/v2/pokemon/3/), Pirate(name=charmander, url=https://pokeapi.co/api/v2/pokemon/4/), Pirate(name=charmeleon, url=https://pokeapi.co/api/v2/pokemon/5/), Pirate(name=charizard, url=https://pokeapi.co/api/v2/pokemon/6/), Pirate(name=squirtle, url=https://pokeapi.co/api/v2/pokemon/7/), Pirate(name=wartortle, url=https://pokeapi.co/api/v2/pokemon/8/), Pirate(name=blastoise, url=https://pokeapi.co/api/v2/pokemon/9/), Pirate(name=caterpie, url=https://pokeapi.co/api/v2/pokemon/10/)])
那我在執行的時候有碰到一個 Error
java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType; ......
那他的解法就是把下面這段 code 加到 app/build.gradle
的 android
區塊中
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
具體為什麼會這樣我還沒去探究,或許知道的大大可以留言開導小弟
那明天會繼續去把 Retrofit 這快做的更完整,希望明天可以準時出文章