随着Google抛弃HttpClient以及Volley的逐步没落,OkHttp越来越受到开发者的青睐。在之前的博文中《OkHttp源码解析》,我们已经对OkHttp的使用和原理进行了分析。Retrofit作为square公司的另一网络利器,则是在OkHttp的基础上,对网络请求做了更加系统、简洁的封装,本博文就来带大家认识和学习Retrofit,将知识转化为战斗力。
本博文基于Retrofit 2.3.0进行讲解
Retrofit是什么?
按照惯例,我们首先来看下GitHub上Retrofit的解释。
Type-safe HTTP client for Android and Java by Square, Inc.
翻译下:
Square公司为Android和Java开发的类型安全的Http Client。
Retrofit其实就是对OkHttp的封装,使用面向接口的方式进行网络请求(大大简化了我们的代码,感觉这一点和Spring中Controller的接口编写类似),它使用动态生成的代理类封装了接口,而且使用很多注解提供功能。
如何使用Retrofit?
//Retrofit本体
compile 'com.squareup.retrofit2:retrofit:2.3.0'
//OkHttp
compile 'com.squareup.okhttp3:okhttp:3.9.0'
//OkHttp依赖的okio
compile 'com.squareup.okio:okio:1.13.0'
//Retrofit Gson转换器
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
忘记告诉你,Retrofit的使用要依赖于OkHttp,所以你还要加入对OkHttp库的依赖(当然别忘了,还要okio)。
当然,如果是Android开发者,不要忘记请求、添加网络权限。
当你做完了如上的动作,我们的武器库就填好了,下面我们就来看看如何使用我们的武器吧。
API
这个章节,我们主要来讨论和学习下如何使用Retrofit。示例都是一些简单的例子,以便我们快速上手Retrofit。
1. 示例
- 定义HTTP API接口
public interface GitHubService {
@GET("users/{user}/repos")
Call> listRepos(@Path("user") String user);
}
Retrofit会使用动态代理的方式,将我们定义的Java Interface转换为网络请求接口,我们只需要定义和使用,暂时不关注它是如何进行转换的。
- 定义Retrofit实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
从这段代码上看,Retrofit的构造同样使用了设计模式中的[建造者模式],可见该模式的使用之普遍。
在构造Retrofit时我们要注意以下几点:
- baseUrl以/结尾。
- @GET等接口URL不要以/开头。
- 转换接口
GitHubService service = retrofit.create(GitHubService.class);
Retrofit使用动态代理的方式,实现了我们定义的GitHubService接口。
- 调用API接口
GitHubService service = retrofit.create(GitHubService.class);
//同步请求
//https://api.github.com/users/zhangshuaisir/repos
Call> call = service.listRepos("zhangshuaisir");
try {
Response> repos = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
//不管同步还是异步请求,call只能执行一次,否则会抛 IllegalStateException
Call> clone = call.clone();
//异步请求
//Android 回调接口执行在Android主线程中
call.enqueue(new Callback>() {
@Override
public void onResponse(Call> call, Response> response) {
for (Repo repo : response.body()) {
System.out.println(repo.toString());
}
}
@Override
public void onFailure(Call> call, Throwable t) {
t.printStackTrace();
}
});
- 终止请求
call.cancel();
即使该请求正在执行,调用该接口仍然可以终止它。
2.Annotation
在上面的示例中,我们看到了[ @GET("users/{user}/repos")]注解,凭借我们的直觉注解代表是GET请求,那么本章节就带大家来认识下Retrofit注解相关内容。
注解的作用:
添加在方法上的注解和参数,代表了Retrofit如何处理这个请求。
- 方法注解
每一个方法都必须包含一个提供请求方法和相对URL的注解,Retrofit内置了五个注解,资源的相对URL在注解中指定。内置的注解有:
- @GET
- @POST
- @PUT
- @DELETE
- @HEAD
这些注解对应了我们请求处理方式,相信大家看完就能理解,在此不再赘述。
2 URL
(1)动态URL
- 请求URL可以使用方法上的替换块和参数动态更新请求URL。
- 替换块是由{}包围的字母数字字符串,使用@Path注释相应的参数。
@GET("group/{id}/users")
Call> groupList(@Path("id") int groupId);
我们可以将{id}理解为占位符,在实际的运行中,该值会被@Path("id")标注的参数替换,从而达到动态更新URL的目的。
@Path注解的定位是替换URL的路径,而不是查询参数替换。
(2)添加查询参数
我们可以通过使用@Query注解来向URL中添加查询参数,示例如下:
@GET("group/{id}/users")
Call> groupList(@Path("id") int groupId, @Query("sort") String sort);
在实际的运行中,@Query("sort")注解会被如此解释:https://api.github.com/users/zhangshuaisir/repos?sort=desc.
对于复杂的查询参数构造,可以使用@QueryMap来处理。
@GET("group/{id}/users")
Call> groupList(@Path("id") int groupId, @QueryMap Map options);
@Query和@QueryMap注解定位于URL中查询参数的替换,与@Path(URL路径替换)的作用和定位不同,使用时要注意。
- 请求体
一个对象可以通过使用@Body注解作为HTTP request body使用。
@POST("users/new")
Call createUser(@Body User user);
实体会根据配置在Retrofit的converter进行转换,如果在构造Retrofit的时候我们没有配置converter,那么只能使用RequestBody类型的实体。
- 表单方式传递值
Retrofit支持传递[form-encoded]数据,使用@FormUrlEncoded注解进行标识。
每个key-value值,使用@Field进行标识。
通过@POST指明url,添加FormUrlEncoded,然后通过@Field添加参数即可。
当我们有很多个表单参数时也可以通过@FieldMap注解和Map对象参数来指定每个表单项的Key,value的值。
@FormUrlEncoded
@POST("user/edit")
Call updateUser(@Field("first_name") String first, @Field("last_name") String last);
- 文件上传
Retrofit同样支持Multipart,使用@Mutipart进行注解,Parts使用@Part注解。
@Multipart
@PUT("user/photo")
Call updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
- Header
可以使用@Headers标签静态来指定请求的Header。
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call> widgetList();
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call getUser(@Path("username") String username);
当然我们也可以使用@Header标签动态指定Header。
@GET("user")
Call getUser(@Header("Authorization") String authorization)
3. 同步&异步
Call实例可以执行同步和异步请求,但是每个实例只能执行一次,可以使用clone方法创建一个新的示例。
在Android中,callbacks是执行在主线程中的;在JVM中,callbacks在调用请求的线程中执行。
4.Retrofit配置
CONVERTERS
默认情况下,Retrofit只能将HTTP Body实体转换为OkHttp的ResponseBody类型;
而且它也只能接收RequestBody类型的请求实体。
除了默认的转换器,我们还可以添加自定义的转换器,我们常用的主要有以下几种:
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
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
- 示例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofit.create(GitHubService.class);
如果以上的转换器仍然满足不了你的需求,那么我们可以自定义转换器。
只需要继承Converter.Factory类,我们就可以自定义转换器。
Converter.Factory类是一个抽象类,我们来看下它的定义。
/** Creates {@link Converter} instances based on a type and target usage. */
abstract class Factory {
/**
* 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}
* declaration.
*/
public @Nullable Converter responseBodyConverter(Type type,
Annotation[] annotations, Retrofit retrofit) {
return null;
}
/**
* 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;
}
/**
* 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;
}
/**
* Extract the upper bound of the generic parameter at {@code index} from {@code type}. For
* example, index 1 of {@code Map} returns {@code Runnable}.
*/
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
/**
* Extract the raw class type from {@code type}. For example, the type representing
* {@code List extends Runnable>} returns {@code List.class}.
*/
protected static Class> getRawType(Type type) {
return Utils.getRawType(type);
}
}
关于自定义Converter,有以下几点我们需要注意:
- responseBodyConverter:主要完成ResponseBody到实际的返回类型的转化,这个类型对应Call<\XXX>里面的泛型XXX。
- requestBodyConverter:完成对象到RequestBody的构造。主要是对应@Body注解,其实@Part等注解也会需要requestBodyConverter,只不过我们的参数类型都是RequestBody,由默认的converter处理了。
- 一定要注意,检查type如果不是自己能处理的类型,记得return null (因为可以添加多个,你不能处理return null ,还会去遍历后面的converter).
Proguard
# Platform calls Class.forName on types which do not exist on Android to determine platform.
-dontnote retrofit2.Platform
# Platform used when running on Java 8 VMs. Will not be used at runtime.
-dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions
-dontwarn okio.**