文章

Retrofit学习笔记

*翻译自官方文档(https://square.github.io/retrofit/

Quickstart

Retrofit主要做的事情就是把HTTP API转换成Java接口。

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

Retrofit类产生一个GitHubService接口的实现。

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);

GitHubService中产生的每个Call对象都可以用来向远程Web服务器产生一个同步或异步的HTTP请求。

API声明

请求方法

支持HTTPGETPOST等所有HTTP方法。

@GET("users/list")
// 加上请求参数
@GET("users/list?sort=desc") 

URL填充

支持动态URL,可以通过{}来包裹需要填充的字符串(数字和字母)。在参数中用@Path来指定。

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);

也支持请求参数(用@Query指定)

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);

在复杂查询中可以使用Map(用@QueryMap指定)

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);

请求体(Body)

@Body指定request body。

@POST("users/new")
Call<User> createUser(@Body User user);

该对象会根据Retrofit实例中指定的converter转换。(解释:例如如果指定使用Gson,就会使用Gson对该对象进行转换,即转换成json。)如果没有指定converter,只能使用RequestBody。(?)

表单和multipart

使用@FormUrlEncoded@Multipart。如

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);

这部分用得比较少,不细看了。关于Multipart和Form-urlencoded的区别可以看https://blog.csdn.net/lihefei_coder/article/details/99606386

请求头

@Header标识。

@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);

请求头也可以是动态的。当值为空时,会忽略该请求头。否则将会对其值调用toString

@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

也可以使用Map

@GET("user")
Call<User> getUser(@HeaderMap Map<String, String> headers)

同步和异步

Call实例可以同步或异步执行,每个实例只能使用一次,但clone()会产生一个可以使用的新实例。

在安卓中 ,回调会在主线程中执行。在JVM中,回调会在执行请求的同一个线程中执行。

Retrofit配置

使用Gson:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);

第一行代码中的实例(SunnyWeather)

目录结构

其中PlaceService、WeatherService是绑定Retrofit的接口,ServiceCreator用于创建对应Service的retrofit实例,SunnyWeatherNetwort则是供Repository调用的方法。

PlaceService

WeatherService和PlaceService类似,不重复讲了。

package com.sunnyweather.android.logic.network

import com.sunnyweather.android.SunnyWeatherApplication
import com.sunnyweather.android.logic.model.PlaceResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query

interface PlaceService {

    @GET("v2/place?token=${SunnyWeatherApplication.TOKEN}&lang=zh_CN")
    fun searchPlaces(@Query("query") query: String): Call<PlaceResponse>

}

主要是用GET方法,${SunnyWeatherApplication.TOKEN}表示从配置文件中取token的值。

ServiceCreator

用来创建Retrofit实例。

package com.sunnyweather.android.logic.network

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object ServiceCreator {

    private const val BASE_URL = "https://api.caiyunapp.com/"

    private val retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)

    inline fun <reified T> create(): T = create(T::class.java)

}

inline那一行是泛型优化,暂时没搞懂。

SunnyWeatherNetwork

主要用了挂起函数(suspend)实现异步。

package com.sunnyweather.android.logic.network

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

object SunnyWeatherNetwork {

    private val weatherService = ServiceCreator.create(WeatherService::class.java)

    suspend fun getDailyWeather(lng: String, lat: String) = weatherService.getDailyWeather(lng, lat).await()

    suspend fun getRealtimeWeather(lng: String, lat: String) = weatherService.getRealtimeWeather(lng, lat).await()

    private val placeService = ServiceCreator.create(PlaceService::class.java)

    suspend fun searchPlaces(query: String) = placeService.searchPlaces(query).await()

    private suspend fun <T> Call<T>.await(): T {
        return suspendCoroutine { continuation ->
            enqueue(object : Callback<T> {
                override fun onResponse(call: Call<T>, response: Response<T>) {
                    val body = response.body()
                    if (body != null) continuation.resume(body)
                    else continuation.resumeWithException(RuntimeException("response body is null"))
                }

                override fun onFailure(call: Call<T>, t: Throwable) {
                    continuation.resumeWithException(t)
                }
            })
        }
    }

}
License:  CC BY 4.0