Android Retrofit 学习笔记

基于Retrofit2.6+kotlin协程

基础

导入

1
2
3
4
5
6
# retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
# gson解析器,可以换成其他的json解析
implementation 'com.squareup.retrofit2:converter-gson:2.64.0'
# 与kotlin协程结合,可以换成rxJava+rxAndroid
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'

编写接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface ArticleApi {
@Headers(WindConstants.CONTENT_TYPE_JSON, WindConstants.APP_ID, WindConstants.API_KEY)
@GET("1/classes/ArticleModel")
suspend fun getAllArticle(@Query("skip") skip: Int = 0,
// @Query("limit") limit: Int = 100,
@Query("order") order: String = "-createdAt")
: ApiBean.ApiListResponse<ArticleModel>

@Headers(WindConstants.CONTENT_TYPE_JSON, WindConstants.APP_ID, WindConstants.API_KEY)
@GET("1/classes/ArticleModel")
suspend fun getArticleOfAuthor(@Query("where") where: String, @Query("order") order: String = "-createdAt")
: ApiBean.ApiListResponse<ArticleModel>

@Headers(WindConstants.CONTENT_TYPE_JSON, WindConstants.APP_ID, WindConstants.API_KEY)
@POST("1/classes/ArticleModel")
suspend fun addNewArticle(@Body jsonStr: RequestBody): ApiBean.AddResponse

// 获取单个model
@Headers(WindConstants.CONTENT_TYPE_JSON, WindConstants.APP_ID, WindConstants.API_KEY)
@GET("1/classes/ArticleModel/{objectId}")
suspend fun getArticle(@Path("objectId") objectId: String): ArticleModel
}

创建retrofit实例

1
2
3
4
5
6
7
8
9
10
11
12
13
object ApiManager {

private val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(PrivateConstants.BMOB_API_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory.invoke())
.build()

val articleApi = retrofit.create(ArticleApi::class.java)
val pictureApi = retrofit.create(PictureApi::class.java)
val userApi = retrofit.create(UserApi::class.java)
val commentApi = retrofit.create(CommentApi::class.java)
}

调用接口方法

在api的直接方法之外再封装一层,处理返回数据,把需要的数据部分传给回调进行下一步操作,因为kotlin可以使用方法作为参数因此这里可以不用写回调函数了。

1
2
3
4
suspend fun getAllArticle(next: (List<ArticleModel>) -> Unit) {
val response = ApiManager.articleApi.getAllArticle()
next(response.results)
}

最外层直接调用,可以在UI层,在MVVM架构中应该在Repository层编写一个方法在其中调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun loadArticleList() {
// 使用数据库(可选)
if (articleList == null) {
articleList = articleDao.loadAll()
}
CoroutineScope(Dispatchers.IO).launch {
ArticleService.getAllArticle { articles ->
// 对结果进行数据处理
articles.forEach { it.lastUpdateTime = System.currentTimeMillis() }
// 保存结果或进行其他操作
articleDao.clear()
articleDao.saveAll(articles)
}
}

}

最后在UI层通过viewModel调用该方法。

配置

基础链接与解析器

1
2
3
4
5
private val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(PrivateConstants.BMOB_API_URL) // 设置链接
.addConverterFactory(GsonConverterFactory.create()) // 设置jsonConverter
.addCallAdapterFactory(CoroutineCallAdapterFactory.invoke()) // 设置把回调转换成kotlin协程
.build()

Header参数

使用注解-固定参数

在接口方法上用注解的方式添加header:

1
@Headers(WindConstants.CONTENT_TYPE_JSON, WindConstants.APP_ID, WindConstants.API_KEY)

其中:

1
2
3
const val CONTENT_TYPE_JSON = "Content-Type:application/json"
const val APP_ID = "X-Bmob-Application-Id:${PrivateConstants.APP_ID}"
const val API_KEY = "X-Bmob-REST-API-Key:${PrivateConstants.API_KEY}"

使用注解-可变参数

使用注解但把header作为方法参数传递:

1
2
3
4
5
6
7
// 更新用户信息
@Headers(WindConstants.CONTENT_TYPE_JSON, WindConstants.APP_ID, WindConstants.API_KEY) // 固定header
@PUT("1/users/{objectId}")
suspend fun saveUser(@Header("X-Bmob-Session-Token") header: String, // 添加key为X-Bmob-Session-Token的header,value为传递的参数值
@Path("objectId") objectId: String,
@Body jsonStr: RequestBody):
ApiBean.UpdateResponse

使用拦截器添加header等参数

拦截器除了添加header还能对传入的参数进行一系列处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 定义一个拦截器
private val httpClientBuilder = OkHttpClient.Builder().addInterceptor { chain -> // 拦截器拦截的初始请求
val paramsMap = baseParamsMap.clone() as HashMap<String, String>

val oldRequest = chain.request() // 拿到旧的请求内容
val oldUrl = oldRequest.url() // 旧的请求链接
val queryKeys = oldUrl.queryParameterNames() // 获取链接中带的参数
for ((index, key) in queryKeys.withIndex()) {
paramsMap[key] = oldUrl.queryParameterValue(index)
}

// 在旧的链接上添加新的参数
val urlBuilder = oldUrl.newBuilder()
.addQueryParameter("appId", AppInfo.instance.appID)
.addQueryParameter("appKey", AppInfo.instance.appKey)
.addQueryParameter("deviceId", EncodeUtil.deviceID)
.addQueryParameter("requestId", requestId)
.addQueryParameter("systemVersion", Build.VERSION.SDK_INT.toString())
.addQueryParameter("platformString", Build.BRAND + " " + Build.MODEL)
.addQueryParameter("channelId", "abcd")
.addQueryParameter("v", versionCode)

// 给旧的请求添加新的链接生成新的请求
val newRequest = oldRequest.newBuilder()
.header("User-Agent", "Your-App-Name") // 自定义添加header
.header("Accept", "application/vnd.yourapi.v1.full+json")
.method(oldRequest.method(), oldRequest.body()) // method和body保持原样
.url(urlBuilder.build())
.build()

if (isDebug) {
Log.i("mojulist", newRequest.url().url().toString())
}
chain.proceed(newRequest) // 请求继续
}

因为Retrofit底层使用的是okhttp,可以自定义一个OkHttpClient,在实例化Retrofit时指定。因此可以通过自定义这个Client来实现自定义api请求。