Retrofit
A type-safe HTTP client for Android and Java
Rerofit是Andorid最流行的HTTP框架,可以帮助开发者用最简单的代码实现API的调用。
本文主要介绍Kotlin中Retrofit的基本使用方法, 以帮助初学者快速入门
retrofit完整的使用涉及下面几个步骤:
- gradle配置
- 实现interfact
- 定义Model
- 实现retrofit对象
- 使用retrofit发送请求
1. gradle
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'
2. interface
使用inerface定义请求用的API:
https://randomuser.me/api
上述API用interface定义后如下:
interface IApiService {
@GET("api")
fun apiDemo(): Call<RandomUserDemo>
}
interface的定义由以下元素构成
2.1 Request Method
通过@GET、@PUT注解可以定义HTTP的Request Method,注解中可以添加参数指定请求的path
@GET("users/list") //参数中指定URL的Path
@GET("users/list?sort=desc") //Path最后附带Query词
Path
URL的Path可以通过方法参数动态配置,如下,@Path注解的参数会传入到{...}中。
@GET("group/{id}/users")
fun groupList(@Path("id") groupId : Int) : Call<List<User>>
Query
Path末尾附带?的Query词,也同样可以通过参数动态传入,使用@Query注解,如下
@GET("group/{id}/users")
fun groupList(@Path("id") groupId: Int, @Query("sort") sort: String): Call<List<User>>
最终会转成以下URL
https://randomuser.me/group/3/users?sort=desc
当没有需要动态配置的内容,也就是方法可以没有参数时,在Kotlin中可以用变量代替方法,将Request Method注解声明在变量的Cusotm Getter方法上,如下:
@get:GET("api")
val apiDemo: Call<RandomUserDemo>
2.2 Request Body
Request Body用来存放Post的数据,@Body注解将一个JavaBean自动转换成Key-Value的Json格式作为Post的Body数据
@POST("users/new")
fun createUser(@Body user: User): Call<User>;
Form Encode
请求参数希望以表单数据形式(Key-Value)发送时,使用@FormUrlEncoded注解方法,@Field注解参数
@FormUrlEncoded
@POST("user/edit")
fun updateUser(@Field("first_name") first: String, @Field("last_name") last: String): Call<User>
Maltipart
希望发送Maltipart数据时,使用@Multipart注解方法,@Part注解参数
@Multipart
@PUT("user/photo")
fun updateUser(@Part("photo") photo: RequestBody, @Part("description") description: RequestBody): Call<User>
需要注意的是@Body、@FormUrlEncoded、@Maltipart作用相似,都是用来指定数据的发送方式,所以三者不能同时使用
2.3 Header
使用@Header注解可以配置HTTP请求的Header
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
fun widgetList(): Call<List<Widget>>;
Header中可以同时配置多个项目
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
fun getUser(@Path("username") username: String): Call<User>
也可以通过参数进行动态配置
@GET("user")
fun getUser(@Header("Authorization") authorization: String): Call<User>
3.Model Class
根据HTTP的request/reponse中的Json数据需要定义本地对应的JavaBean:
{
"info": {
"page": 1,
"results": 1,
"seed": "7aed050e936ddbda",
"version": "1.1"
},
"results": [
{
"cell": "0486-036-722",
"dob": "1979-04-08 06:14:29",
"email": "roger.holt@example.com",
"gender": "male",
"id": {
"name": "TFN",
"value": "023064228"
},
"location": {
"city": "port macquarie",
"postcode": 1650,
"state": "western australia",
"street": "3769 dogwood ave"
},
"login": {
"md5": "e7f8bb220a82a28ad9375081f022ff2f",
"password": "aaaaaaa",
"salt": "Q6J1Te1h",
"sha1": "e2ec839f88e8fdaddbb2a83877d9afd8342bf26c",
"sha256": "a602c4c64dc6fc1b94d6c98eedbf4383d79cd8894e21cc9f37f9fca3000cea05",
"username": "greenduck262"
},
"name": {
"first": "roger",
"last": "holt",
"title": "mr"
},
"nat": "AU",
"phone": "09-2659-5113",
"picture": {
"large": "https://randomuser.me/api/portraits/men/78.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/78.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/78.jpg"
},
"registered": "2004-12-21 07:19:21"
}
]
}
针对上面的Json数据,使用data class定义Model类如下:
data class RandomUserDemo(var info: Info,
var results: List<Result>)
data class Info(var seed: String,
var results: Int,
var page: Int,
var version: String)
data class Result(var gender: String,
var email: String,
var registered: String,
var dob: String,
var phone: String,
var cell: String)
3.1 Custom Key Name
在data class转换Json的时候,可以通过@SerializedName改变key的名字
data class Result(var gender: String,
var email: String,
var registered: Int,
var dob: Int,
var phone: Int,
var cell: Int,
@SerializedName("id") var idMap: IdMap)
data class IdMap(var name: String,
var value: String)
注意这并不是retrofit提供的注解,而是gson的功能。retrofit的实际使用中经常使用Gson来负责Json转换部分,所以两个库经常一起使用
3.2 Transient
Model中不需要参与请求的成员,可以添加@Transient注解,因为@Transient注解的内容不会参与json转换中的序列化过程。注意transient不是gson或retrofit提供的,是jvm自带的特性,在java和kotlin中的用法略有不同,kotlin中是注解,java中是关键字。
public class IdMap {
public long transient id = 0;
}
data class IdMap(@Transient var id: Long,
var name: String,
var value: String)
4. Retorfit
4.1 HttpClient
可以通过HttpClient完成一些基本配置,在Retrofit对象的创建中使用
val httpBuilder: OkHttpClient.Builder get() {
// create http client
val httpClient = OkHttpClient.Builder()
.addInterceptor(Interceptor { chain ->
val original = chain.request()
//header
val request = original.newBuilder()
.header("Accept", "application/json")
.method(original.method(), original.body())
.build()
return@Interceptor chain.proceed(request)
})
.readTimeout(30, TimeUnit.SECONDS)
// log interceptor
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
httpClient.addInterceptor(loggingInterceptor)
return httpClient
}
httpClient用来进行一些基本配置,例如配置header,添加日志等。
4.2 Retorfit
// core for controller
val service: IApiService = create(IApiService::class.java)
lateinit var retrofit: Retrofit
fun <S> create(serviceClass: Class<S>): S {
val gson = GsonBuilder()
.serializeNulls()
.create()
// create retrofit
retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http://your_base_url/") // Put your base URL
.client(httpBuilder.build())
.build()
return retrofit.create(serviceClass)
}
创建retorfit对象时,设置baseUrl、设置数据转换方式(gson)、并可以设置一个httpClient进行基础配置(这并不是必须的)。最重要的是通过interface(IApiService),返回一个IApiService同类型的动态代理对象,通过这个对象业务层可以使用IApiService中定义的方法发送请求。
4.3 singleton
retrofit对象一般以单例存在供全局调用,无需反复创建。例如,在dagger中可以如下配置
@Singleton
@Provides
internal fun providesApiService(): IApiService {
return ApiService().service
}
5. 使用
retrofit对象创建后,可以对其进行同步或异步调用
5.1 同步
try {
val response = API.apiDemo().execute()
if (response.isSuccessful()) {
return response.body()
} else {
// failed
}
} catch (e: IOException e) {
e.printStackTrace()
}
5.2 异步
API.apiDemo().enqueue(object : Callback<RandomUserDemo>() {
fun onResponse(call: Call<RandomUserDemo>?, response: Response<RandomUserDemo>) {
val demo: RandomUserDemo? = response.body()
}
fun onFailure(call: Call<RandomUserDemo?>?, t: Throwable?) {
}
})
最后
本文大量篇幅放在Retrofit的创建过程,作为入门文章不想引入过多额外知识点,在实际使用中Retrofit更多地是配合RxJava使用,返回一个Observable/Single类型结果而非Call类型,Retrofit也可以返回LiveData、Flow等其他类型的结果,根据需求灵活配置也正是Retrofit的优势之一。