目录
1.Retrofit是什么?
2.Retrofit如何使用?
2.1创建HTTP请求的API接口
2.2请求执行
3.注解详情
3.1请求方法注解
3.2标记请求数据类型
3.3注解参数
4.GSON和Converter
5.RxJava和CallAdapter
6.自定义Converter
7.自定义CallAdapter
8.其他说明
8.1Retrofit.Builder
8.2Retrofit的Url组合规则
retrofit网络请求相关依赖包说明
implementation 'com.google.code.gson:gson:2.8.0'(gson生成和解析库)
implementation 'com.squareup.okhttp3:okhttp:3.9.1'(开源的网络请求库)
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'(支持okhttp跟踪到一个网络请求的所有状态,包括请求头、请求体、响应行、 响应体,方便调试)
implementation 'com.squareup.retrofit2:retrofit:2.3.0'(实现将HTTP请求转换为Java接口)
implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'(配合Rxjava 使用)
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'(转换器,请求结果转换成Model)
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex:rxjava:1.2.1'(一种帮助你做异步的框架. 类似于 AsyncTask. 但其灵活性和扩展性远远强于前者. 从能力上讲, 如果说 AsycnTask 是 DOS 操作系统, RxJava 是 Window 操作系统。)
网络请求的大概分为四部分,retrofit主要负责封装网络请求,okhttp负责完成网络请求,logging-interceptor负责网络请求和响应的输出日志方便调试,rxjava一种帮助你做异步的框架,gson负责json的生成和解析,将请求结果转为相应的Model;
Retrofit官网:https://square.github.io/retrofit/#restadapter-configuration
Retrofit将您的HTTP API转换为Java接口;Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责网络请求接口的封装;
public static final ApiService service = new Retrofit.Builder()
.baseUrl("http://www.baidu.com")
.build()
.create(ApiService.class);
1)通过Retrofit.Builder内部类创建Retrofit实例,需要调用baseUrl(url)指定请求的跟路径和调用build()方法执行Retrofit实例创建,在build()方法内部会创建Retrofit需要的其他默认参数;
注意:下面三种方式设置URL都可以,但是推荐第三种方式设置,当把url传入给Retrofit对URL进行解析,HttpUrl会解析请求的协议(http和https,或者其他协议),域名,端口等信息,实际上HttpUrl会为域名后面加上“/”;
"http://www.baidu.com/?key=value"
"http://www.baidu.com"
"http://www.baidu.com/" //推荐格式
最终请求的时候跟路径会处理为"http://www.baidu.com/"格式;
http://www.baidu.com/?c=api&a=getList&p=1&model=1&page_id=0&create_time=0&client=android&version=1.3.0&time=1584694243&device_id=000000000000000&show_sdv=1
2)调用Retrofit类create()创建接口ApiService实例
创建接口ApiService实例时借助Java提供Proxy动态代理类实现ApiService代理和实例的创建;
public interface ApiService {
@GET("group/{id}/users")
Call groupList(@Path("id") int groupId);
}
//请求回调在UI线程执行
apiService.groupList(23)
.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
try {
response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call call, Throwable t) {
t.printStackTrace();
}
});
既然涉及HTTP的请求接口,则需要涉及HTTP请求的方法;
* The relative path for a given method is obtained from an annotation on the method describing
* the request type. The built-in methods are {@link retrofit2.http.GET GET},
* {@link retrofit2.http.PUT PUT}, {@link retrofit2.http.POST POST}, {@link retrofit2.http.PATCH
* PATCH}, {@link retrofit2.http.HEAD HEAD}, {@link retrofit2.http.DELETE DELETE} and
* {@link retrofit2.http.OPTIONS OPTIONS}. You can use a custom HTTP method with
* {@link HTTP @HTTP}. For a dynamic URL, omit the path on the annotation and annotate the first
* parameter with {@link Url @Url}.
请求方法 | 类 | 备注说明 |
GET | retrofit2.http.GET |
HTTP的请求方法,请求路径是baseurl和GET等方法指定相对路径组成一个完整路径;通过@Path方法可以动态设置路径中指定参数{id};@Url可以动态替换相对路径,同时也可以@Path方法可以动态设置路径中指定参数{id}; |
POST | retrofit2.http.POST | |
PATCH | retrofit2.http.PATCH | |
HEAD | retrofit2.http.HEAD | |
DELETE | retrofit2.http.DELETE | |
OPTIONS | retrofit2.http.OPTIONS | |
HTTP | retrofit2.http.HTTP | HTTP请求可以指定以上所有请求方法,同时也可以@Path方法可以动态设置路径中指定参数{id}; |
下面时通过GET方法和HTTP定义的请求接口;
@GET("group/{id}/users")
Observable groupList(@Path("id") int groupId);
@HTTP(method = "GET", path = "group/{id}/users",hasBody = false)
Observable groupList(@Path("id") int groupId);
//通过Url动态设置相对路径,请求方法不设置相对路径,@Url指定的动态url参数做为第一个参数
@GET()
Observable groupList(@Url String url, @Path("id") int groupId);
* {@link retrofit2.http.FormUrlEncoded @FormUrlEncoded} - Form-encoded data with key-value
* pairs specified by the {@link retrofit2.http.Field @Field} parameter annotation.
* {@link retrofit2.http.Multipart @Multipart} - RFC 2388-compliant multipart data with
* parts specified by the {@link retrofit2.http.Part @Part} parameter annotation.
数据类型 | 类 | 备注说明 |
FormUrlEncoded | retrofit2.http.FormUrlEncoded | 标记为表单请求,只能上传键值对,并且键值对都是间隔分开的,只是最后会转化为一条信息; Content-Type:application/x-www-from-urlencoded |
Multipart | retrofit2.http.Multipart | 既可以上传文件等二进制数据,也可以上传表单键值对; Content-Type:application/form-data |
Streaming | retrofit2.http.Streaming | 从字面意思得知,只可以上传二进制数据,通常用来上传文件,由于没有键值,所以,一次只能上传一个文件;表示响应体是以数据流的返回,如果没有此注解,默认会把数据全部加载到内存中,读取数据时也是从内存中读取,所有返回的数据比较大时,需要此注解;Content-Type:application/octet-stream |
@FormUrlEncoded
@POST("user/edit")
Call updateUser(@Field("first_name") String first, @Field("last_name") String last);
@Multipart
@PUT("user/photo")
Call updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
分类 | 名称 | 备注 |
作用于方法 | Headers | 你能通过@Headers注解设置静态的Headers(请求头) |
作用于方法参数 | Header | 一个请求的Header能够通过@Header注解来实现动态更新; |
HeaderMap | 对于复杂的动态header组合,可以通过Map | |
Field | FormUrlEncoded与Field和FieldMap(Map
|
|
FieldMap | ||
Part | ||
PartMap | ||
Body | 非表单请求体,通过@Body注解可以指定一个对象作为HTTP的请求; | |
Query | 表示为查询参数; | |
QueryMap | 表示为查询参数集合;(Map |
|
Path | 指定变量值做为请求路径,在请求路径并表示{id}; | |
Url | 指定动态请求的相对路径,需要做为第一个参数,请求方法里面不设置路径; |
注意:
1)Query和QueryMap与Field和FieldMap,Query会放置在url上请求服务器,Field做为请求体传给服务器,但生成的数据形式一样的;
2)Query、Field和Part这三者都支持数组和实现了Iterable接口的类型,如List,Set等,方便向后台传递数组;
Call foo(@Query("ids[]") List ids);
//结果:ids[]=0&ids[]=1&ids[]=2
以上注解的具体使用都可以参考相应类的表头有明确的使用说明,例如QueryMap:
示例:
GET("/friends")
Call friends(@QueryMap Map filters);
调用方式foo.friends(ImmutableMap.of("group", "coworker", "age", "42"));
请求路径/friends?group=coworker&age=42
默认key和value是进行URL编码的;
通过encoded=true变量可以改变编码行为;
GET("/friends")
Call friends(@QueryMap(encoded=true) Map filters);
调用方式foo.friends(ImmutableMap.of("group", "coworker+bowling"));
请求路径/search?group=coworker+bowling
其他注解具体使用参考相应类注解说明;
//converters 被添加的顺序将是它们被Retrofit尝试的顺序
public static final ApiService service = new Retrofit.Builder()
.baseUrl("http://www.baidu.com")
.client(HttpUtils.client)
.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(EntityUtils.gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
// .callbackExecutor()
// .callFactory()
// .validateEagerly()
.build()
.create(ApiService.class);
默认Retrofit支持ResponseBody响应体,调用API方法返回Call
Call
@GET("group/{id}/users")
Call>> groupList(@Path("id") int groupId);
1)如何实现这种效果,这时就需要借助Gson转换器,现引入gson和转换gson库;
implementation 'com.google.code.gson:gson:2.8.0'(gson生成和解析库)
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'(转换器,请求结果转换成Model)
2)创建Gson转换器
创建Gson对象
public final class EntityUtils {
private EntityUtils() {}
public static final Gson gson = new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter())
.create();
}
添加Gson转换器
Retrofit.addConverterFactory(GsonConverterFactory.create(EntityUtils.gson))
这样就可以实现ResponseBody和Gson转换;
Call>>data = apiService.groupList(23)
引入RxJava类库:
implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'(配合Rxjava 使用)
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex:rxjava:1.2.1'(一种帮助你做异步的框架. 类似于 AsyncTask. 但其灵活性和扩展性远远强于前者.
从能力上讲, 如果说 AsycnTask 是 DOS 操作系统, RxJava 是 Window 操作系统。)
(4.GSON和Converter)这一章节主要说明Call
1)添加CallAdapter
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
2)定义API接口:
/**
* 分类列表
* @param c
* @param a
* @param page
* @param model(0:首页,1:文字,2:声音,3:影像,4:单向历)
* @param pageId
* @param time
* @param deviceId
* @param show_sdv
* @return
*/
// @GET("/")
@HTTP(method = "get", path = "/",hasBody = false)
Observable>> getList(@Query("c") String c, @Query("a") String a, @Query("p") int page, @Query("model") int model, @Query("page_id") String pageId, @Query("create_time") String createTime, @Query("client") String client, @Query("version") String version, @Query("time") long time, @Query("device_id") String deviceId, @Query("show_sdv") int show_sdv);
3)调用API接口
apiService.getList("api","getList",page,model,pageId,createTime,"android","1.3.0", TimeUtil.getCurrentSeconds(), deviceId,1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
view.showOnFailure();
}
@Override
public void onNext(Result.Data> listData) {
int size = listData.getDatas().size();
if(size>0){
view.updateListUI(listData.getDatas());
}else {
view.showNoMore();
}
}
});
像上面的这种情况最后我们无法获取到返回的Header和响应码的,如果我们需要这两者,提供两种方案:
1、用Observable
2、用Observable
自定义转换器,实现ResponseBody转换为String类型,Call
Retrofit提供了转换器Converter接口和内部Factory类;
/**
* 转换HTTP的响应头和请求头到指定对象类型,通过Factory方法创建Converter实例,调用Retrofit.Builder#addConverterFactory(Factory)安装转换器给Retrofit实例;
*/
public interface Converter {
//实现F(from)类型转换为T(to)指定数据类型
T convert(F value) throws IOException;
/** 创建基于目标需要的Converter实例 */
abstract class Factory {
/**
* 返回一个转换HTTP响应体到指定数据类型的转换器,假如无法处理则返回null
* 主要处理HTTP响应体
*/
public @Nullable Converter responseBodyConverter(Type type,
Annotation[] annotations, Retrofit retrofit) {
return null;
}
/**
* 返回一个转换HTTP请求体到指定数据类型的转换器,假如无法处理则返回null
* 主要处理HTTP请求体
*/
public @Nullable Converter, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
/**
* 返回一个转换相应类型到指定数据类型的转换器,假如无法处理则返回null
主要处理Field,FieldMap,Header,HeaderMap,Path,Query,QueryMap数值
*/
public @Nullable Converter, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
}
}
1)实现转换器
我们要想从Call
public class StringConverter implements Converter {
//实现转换器方法,将ResponseBody转换为String类型
@Override
public String convert(ResponseBody value) throws IOException {
return value.string();
}
}
2)实现转换器工厂方法
public class StringConverterFactory extends Converter.Factory {
public static StringConverterFactory create(){
return new StringConverterFactory();
}
@Override
public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == String.class) {
return new StringConverter(); //在转换器工厂创建转换器
}
//其它类型我们不处理,返回null就行
return null;
}
}
3)添加转换器
.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(EntityUtils.gson))
注意:converters 被添加的顺序将是它们被Retrofit尝试的顺序,如果有多个ConverterFactory都支持同一种类型,那么就是只有第一个才会被使用,而GsonConverterFactory是不判断是否支持的,所以这里交换了顺序还会有一个异常抛出,原因是类型不匹配;
只要返回值类型的泛型参数就会由我们的StringConverter处理,不管是Call
如果需要自定义转换器可以按照需要自定义转换器即可;
若需要自定义CallAdapter,需要实现CallAdapter接口和相应的工厂方法;
package retrofit2;
//适配Call转换为T类型
public interface CallAdapter {
/**
* Type对应Call中R类型
* 返回此适配器(adapter)在将HTTP响应主体转换为Java时使用的值类型对象。
* 例如:Call数据类型是R 。
* 这个 R 会作为Converter.Factory.responseBodyConverter 的第一个参数
可以参照上面的自定义Converter
*/
Type responseType();
/**
* 返回代理Call类型T实例
* 例如, 给予T类型Async,当调用call运行这个实例将返回Async
* Override
* public Async adapt(final Call call) {
* return Async.create(new Callable>() {
* Override
* public Response call() throws Exception {
* return call.execute();
* }
* });
* }
*/
T adapt(Call call);
//用于向Retrofit提供CallAdapter的工厂类
abstract class Factory {
// 返回
// 在这个方法中判断是否是我们支持的类型,returnType 即Call和Observable
// RxJavaCallAdapterFactory 就是判断returnType是不是Observable> 类型
// 不支持时返回null
public abstract @Nullable CallAdapter, ?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit);
// 用于获取泛型的参数 如 Call 中 Requestbody
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
// 用于获取泛型的原始类型 如 Call 中的 Call
// 上面的get方法需要使用该方法。
protected static Class> getRawType(Type type) {
return Utils.getRawType(type);
}
}
}
了解了CallAdapter的结构和其作用之后,我们就可以开始自定义我们的CallAdapter了,本节以CustomCall
1)定义Call的转换类
在此我们需要定义一个CustomCall,不过这里的CustomCall作为演示只是对Call的一个包装,并没有实际的用途。
public static class CustomCall {
public final Call call;
public CustomCall(Call call) {
this.call = call;
}
public R get() throws IOException {
return call.execute().body();
}
}
2)定义CallAdapter的实现类
有了CustomCall,我们还需要一个CustomCallAdapter来实现 Call
public static class CustomCallAdapter implements CallAdapter> {
private final Type responseType;
// 下面的 responseType 方法需要数据的类型
CustomCallAdapter(Type responseType) {
this.responseType = responseType;
}
@Override
public Type responseType() {
return responseType;
}
@Override
public CustomCall adapt(Call call) {
// 由 CustomCall 决定如何使用
return new CustomCall<>(call);
}
}
3)定义工厂方法
提供一个CustomCallAdapterFactory用于向Retrofit提供CustomCallAdapter:
public static class CustomCallAdapterFactory extends CallAdapter.Factory {
public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();
@Override
public CallAdapter> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
// 获取原始类型
Class> rawType = getRawType(returnType);
// 返回值必须是CustomCall并且带有泛型
if (rawType == CustomCall.class && returnType instanceof ParameterizedType) {
Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);
return new CustomCallAdapter(callReturnType);
}
return null;
}
}
4)注册工厂方法
使用addCallAdapterFactory向Retrofit注册CustomCallAdapterFactory
addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)
注: addCallAdapterFactory与addConverterFactory同理,也有先后顺序。
前面用到了 Retrofit.Builder 中的baseUrl、addCallAdapterFactory、addConverterFactory、build方法,还有callbackExecutor、callFactory、client、validateEagerly这四个方法没有用到,这里简单的介绍一下。
方法 | 用途 |
callbackExecutor(Executor) | 指定Call.enqueue 时使用的Executor ,所以该设置只对返回值为Call 的方法有效 |
callFactory(Factory) | 设置一个自定义的 |
client(OkHttpClient) | 设置自定义的
|
validateEagerly(boolean) | 是否在调用create(Class) 时检测接口定义是否正确,而不是在调用方法才检测,适合在开发、测试时使用 |
BaseUrl | 和URL有关的注解中提供的值 | 最后结果 |
http://localhost:4567/path/to/other/ | /post | http://localhost:4567/post |
http://localhost:4567/path/to/other/ | post | http://localhost:4567/path/to/other/post |
http://localhost:4567/path/to/other/ | https://github.com/ikidou | https://github.com/ikidou |
从上面不能难看出以下规则:
1)如果你在注解中提供的url是完整的url,则url将作为请求的url。
2)如果你在注解中提供的url是不完整的url,且不以 / 开头,则请求的url为baseUrl+注解中提供的值
3)如果你在注解中提供的url是不完整的url,且以 / 开头,则请求的url为baseUrl的主机部分+注解中提供的值
参考:
https://www.jianshu.com/p/308f3c54abdd
https://blog.csdn.net/ahou2468/article/details/104972887