Retrofit
大家都知道 okhttp 是一款由 square 公司开源的 java 版本 http 客户端工具。实际上,square 公司还开源了基于 okhttp 进一步封装的 Retrofit 工具,用来支持通过接口的方式发起 http 请求 。
maven :
<dependency>
<scope>compile</scope>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>2.9.0</version>
</dependency>
注意: Retrofit 只负责调用接口,测试时还是需要自己提供对应的接口。
hello word
- 新建一个接口
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
public interface ArticleServer {
// 实际访问 baseUrl/article/{id}
// 路径中的 {id} 就是借口中的参数 id
@GET("article/{id}")
Call<ResponseBody> getArticle(@Path("id") int id);
@GET("article/list")
Call<ResponseBody> listArticle(@Query("title")String title, @Query("tag")String tag);
}
- 使用
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import java.io.IOException;
public class SquareUpApp {
public static void main(String[] args) throws IOException {
// 创建 Retrofit 对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://127.0.0.1:8085/")
.build();
// 实现前面的接口
ArticleServer service = retrofit.create(ArticleServer.class);
// 同步调用方法
Response<ResponseBody> execute = service.getArticle(2).execute();
System.out.println(execute.code());
System.out.println(execute.message());
System.out.println(execute.raw().request().url());
System.out.println(execute.headers().toString());
System.out.println(execute.body().string());
}
}
ps: Call<ResponseBody> article = service.getArticle(2);
实例只可以被 executed 一次,但是也可以通过 clone()
方法生成新的实例来再次调用。
同步调用与异步调用
同步调用:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://127.0.0.1:8085/")
.build();
ArticleServer service = retrofit.create(ArticleServer.class);
// 同步调用方法
Response<ResponseBody> execute = service.getArticle(2).execute();
System.out.println(execute.code());
System.out.println(execute.message());
System.out.println(execute.raw().request().url());
System.out.println(execute.headers().toString());
System.out.println(execute.body().string());
异步调用:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://127.0.0.1:8085/")
.build();
ArticleServer service = retrofit.create(ArticleServer.class);
// 异步调用方法
service.listArticle("数据", "spring").enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
}
返回泛型
默认情况下,Retrofit 将 http body 反序列化为 OKhttp 的 ResponseBody。
转换器支持其他类型,其 maven(gradle) 依赖如下:
- Gson:
com.squareup.retrofit2:converter-gson
- Jackson:
com.squareup.retrofit2:converter-jackson
- Moshi:
com.squareup.retrofit2:converter-moshi
- Protobuf:
com.squareup.retrofit2:converter-protobuf
- Wire:
com.squareup.retrofit2:converter-wire
- Simple XML:
com.squareup.retrofit2:converter-simplexml
- JAXB:
com.squareup.retrofit2:converter-jaxb
- Scalars (primitives, boxed, and String):
com.squareup.retrofit2:converter-scalars
使用方法,以 gson 为例:
新建实体:
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Article implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String articleId;
private String title;
private String createTime;
private String updateTime;
private String tags;
private Integer replyCount;
private Integer viewCount;
private String posters;
}
新建接口:
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;
import java.util.List;
public interface ArticleServer {
@GET("article/list")
Call<List<Article>> listArticle(@Query("title")String title, @Query("tag")String tag);
}
使用
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://127.0.0.1:8085/")
.build();
ArticleServer service = retrofit.create(ArticleServer.class);
Response<List<Article>> execute = service.listArticle("数据", "spring").execute();
System.out.println(execute.code());
System.out.println(execute.message());
System.out.println(execute.raw().request().url());
System.out.println(execute.headers().toString());
System.out.println(execute.body());
List<Article> body = execute.body();
System.out.println(body);
请求实例
动态路径请求:
@GET("group/{id}/articles")
Call<List<Article>> groupList(@Path("id") int groupId);
查询参数:
@GET("group/{id}/articles")
Call<List<Article>> groupList(@Path("id") int groupId, @Query("sort") String sort);
多个查询参数:
@GET("group/{id}/articles")
Call<List<Article>> groupList(@Path("id") int groupId, @Query("sort") String sort, @Query("tag") String tag);
查询参数作为 map:
@GET("group/{id}/articles")
Call<List<Article>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);
固定参数值:
@GET("article/list?sort=desc")
Call<List<Article>> groupList(@Path("id") int groupId, @Query("title") String title);
body
@POST("article/save")
Call<Article> saveArticle(@Body Article article);
带 header 的请求
固定值的单个 header:
@Headers("Cache-Control: max-age=640000")
@POST("article/save")
Call<Article> saveArticle(@Body Article article);
固定值的多个 header:
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@POST("article/save")
Call<Article> saveArticle(@Header("token") String token, @Body Article article);
变量单个 header:
@POST("article/save")
Call<Article> saveArticle(@Header("token") String token, @Body Article article);
变量多个 header:
@POST("article/save")
Call<Article> saveArticle(@HeaderMap Map<String, String> headers, @Body Article article);
变量和固定值混合使用:
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@POST("article/save")
Call<Article> saveArticle(@HeaderMap Map<String, String> headers, @Body Article article);
表单
使用表单:
@FormUrlEncoded
@POST("article/update")
Call<Article> updateArticle(@Field("id") Long id, @Field("title") String first, @Field("tags") String tags);
上传文件
接口:
注意: 文件的 @Part
注解不要带参数,不然会报错。
@POST("article/uploadFile")
@Multipart
Call<ResponseBody> uploadFile(@Part("title") RequestBody title, @Part("tags") RequestBody tags, @Part MultipartBody.Part file);
实现:
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://127.0.0.1:8085")
.build();
ArticleServer service = retrofit.create(ArticleServer.class);
RequestBody title = RequestBody.create(MediaType.parse("text/plain"), "测试标题");
RequestBody tags = RequestBody.create(MediaType.parse("text/plain"), "spring");
RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), new File("D:\\pdf\\123456.png"));
// myFile 对应接口中接收文件的参数名
MultipartBody.Part filePart = MultipartBody.Part.createFormData("myFile", "123456.png", file);
Response<ResponseBody> execute = service.uploadFile(title, tags, filePart).execute();
System.out.println(execute.code());
自定义 Converter
retrofit2.Converter<F, T>
接口:
public interface Converter<F, T> {
// 实现从 F(rom) 到 T(o)的转换
T convert(F value) throws IOException;
/** Creates {@link Converter} instances based on a type and target usage. */
// 用于向 Retrofit 提供相应 Converter 的工厂
abstract class Factory {
// 处理响应体 - 如果不能处理返回 null
/**
* Returns a {@link Converter} for converting an HTTP response body to {@code type}, or null if
* {@code type} cannot be handled by this factory. This is used to create converters for
* response types such as {@code SimpleResponse} from a {@code Call<SimpleResponse>}
* declaration.
*/
public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
return null;
}
// 对 Part、PartMap、Body 注解的处理
/**
* Returns a {@link Converter} for converting {@code type} to an HTTP request body, or null if
* {@code type} cannot be handled by this factory. This is used to create converters for types
* specified by {@link Body @Body}, {@link Part @Part}, and {@link PartMap @PartMap} values.
*/
public @Nullable Converter<?, RequestBody> requestBodyConverter(
Type type,
Annotation[] parameterAnnotations,
Annotation[] methodAnnotations,
Retrofit retrofit) {
return null;
}
// 对 Field、FieldMap、Header、Path、Query、QueryMap 注解的处理
/**
* Returns a {@link Converter} for converting {@code type} to a {@link String}, or null if
* {@code type} cannot be handled by this factory. This is used to create converters for types
* specified by {@link Field @Field}, {@link FieldMap @FieldMap} values, {@link Header @Header},
* {@link HeaderMap @HeaderMap}, {@link Path @Path}, {@link Query @Query}, and {@link
* QueryMap @QueryMap} values.
*/
public @Nullable Converter<?, String> stringConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
return null;
}
...
}
}
想从 Call 转换为自定义的 Call 那么泛型的 F 和 T 则分别对应 ResponseBody 和 String,那么新建自定义 Converter 如下:
import okhttp3.ResponseBody;
import retrofit2.Converter;
import java.io.IOException;
public class MyConverter implements Converter<ResponseBody, String> {
public static final MyConverter INSTANCE = new MyConverter();
@Override
public String convert(ResponseBody value) throws IOException {
return value.string();
}
}
还需要一个 Factory 来向 Retrofit 注册自定义的 MyConverter :
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
public class MyFactory extends Converter.Factory {
public static final MyFactory INSTANCE = new MyFactory();
public static MyFactory create() {
return INSTANCE;
}
// 只关实现从ResponseBody 到 String 的转换,所以其它方法可不覆盖
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == String.class) {
return MyConverter.INSTANCE;
}
// 其它类型不处理,返回null就行
return null;
}
}
定义测试接口:
@GET("article/list")
Call<String> listJson(@Query("title") String title, @Query("tag") String tag);
使用 Factory 测试:
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(MyFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://127.0.0.1:8085/")
.build();
ArticleServer service = retrofit.create(ArticleServer.class);
Response<String> execute = service.listJson("数据", "spring").execute();
System.out.println(execute.code());
System.out.println(execute.message());
System.out.println(execute.raw().request().url());
System.out.println(execute.headers().toString());
System.out.println(execute.body());
String body = execute.body();
System.out.println(body);
注意: addConverterFactory()
是有先后顺序的,如果有多个 ConverterFactory 都支持同一种类型,那么就是只有第一个才会被使用,而 GsonConverterFactory 是不判断是否支持的,所以这里交换了顺序还会有一个异常抛出,原因是类型不匹配。只要返回值类型的泛型参数就会由 MyConverter 处理,不管是 Call 还是 Observable