Retrofit 是 Square 推出的 HTTP 框架,主要用于 Android 和 Java。Retrofit 将网络请求变成方法的调用,使用起来非常简洁方便。
retrofit模型如
3.Retrofit各个注解的含义及作用?
2.android retrofit 请求参数格式RequestBody的方法
1.retrofit的模型?
3.Retrofit各个注解的含义及作用?
小结:
发现Retrofit并不像理想中的那么好,这里说的不好不是说代码架构不好,而是单指易用性这个方面,所有会用Retrofit的朋友,都知道如何使用Retrofit发送一个请求:
需要写至少一个接口,然后定义至少一个接口方法 ,然后构建Retrofit,然后调用create方法生成接口类,
然后调用enqueue或者 execute方法发送一个异步或同步请求
一个简单的请求一共经历了5步,这还不算完:
你需要添加json解析器,如GsonConvertFactory,来自动序列化json串 - 你需要配置统一的cookie拦截器,这些代码需要你自己编写(网上复制粘贴的除外)。
一般你还需要添加日志拦截器,因为在debug的时候你会发现,Retrofit竟然他妈的不能拼接出完整的url请求地址(完整的请求地址包括请求的主机地址,接口名,所有请求参数拼接,Retrofit最多只能看到接口,后面拼接的参数是看不到的,这在调试的时候很让人不爽)。
如果你说这些都不是事,那么我们再看:
Retrofit提供了MultiPart注解,说明我们可以上传文件,又提供了Streaming注解,说明我们可以下载文件,我们知道Retrofit可以干这些事,但是我们还是没有办法直接写上传下载代码,这些东西都需要我们自己去封装,这也是为什么目前有很多基于Retrofit构建的二次封装库的原因。
很多人用Retrofit基本上也就会发送一个get或者post请求,也就熟悉个别几个作用于参数的注解,
如果你让这些人用Retrofit去搞定所有RESTful风格的接口,可能大部分人直接懵逼,
因为他们不知道各个方法的注解和参数的注解怎么搭配使用才能完美运行,而不是抛出异常,因为Retrofit制定的这些规则你必须遵守。
有些狂热的Retrofit粉丝大呼Retrofit有着插拔式设计,想用就用,想不用就不用,耦合很低,这确实是Retrofit的优点,但也正是因为足够灵活,导致易用性不够,不然也不会产生这么多基于Retrofit构建的框架了。
注意事项:
1,以上部分注解真正的实现在ParameterHandler类中,,每个注解的真正实现都是ParameterHandler类中的一个final类型的内部类,
每个内部类都对各个注解的使用要求做了限制,比如参数是否可空,键和值是否可空等.
2, FormUrlEncoded注解和 Multipart注解不能同时使用,否则会抛出methodError(“Only one encoding annotation is allowed.”);可在ServiceMethod类中parseMethodAnnotation()方法中找到不能同时使用的具体原因.
3, Path注解与Url注解不能同时使用,否则会抛出parameterError(p, “@Path parameters may not be used with @Url.”),可在ServiceMethod类中parseParameterAnnotation()方法中找到不能同时使用的具体代码.
其实原因也很好理解: Path注解用于替换url路径中的参数,这就要求在使用path注解时,必须已经存在请求路径,不然没法替换路径中指定的参数啊,
而Url注解是在参数中指定的请求路径的,这个时候指定请求路径已经晚了,path注解找不到请求路径,更别提更换请求路径中的参数了.
4,对于FiledMap,HeaderMap,PartMap,QueryMap这四种作用于方法的注解,其参数类型必须为Map的实例,且key的类型必须为String类型,
否则抛出异常(以PartMap注解为例):parameterError(p, “@PartMap keys must be of type String: ” + keyType);
5, 使用Body注解的参数不能使用form 或multi-part编码,
即如果为方法使用了FormUrlEncoded或Multipart注解,则方法的参数中不能使用Body注解,否则抛出异常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”);
本篇文章基于retrofit-2.1进行分析.
方法注解:@GET、@POST、@PUT、@DELETE、@PATH、@HEAD、@OPTIONS、@HTTP。
标记注解:@FormUrlEncoded、@Multipart、@Streaming。
参数注解:@Body; @Field、@FieldMap; @Query,@QueryMap;@Part,@PartMap。
其他注解:@Path;@Header,@Headers;@Url
一.方法注解:
1.1 GET注解:用于发送一个get请求,GET注解一般必须添加相对路径或绝对路径或者全路径。
如果不想在GET注解后添加请求路径, 则可以在方法的第一个参数中用@Url注解 添加请求路径。
1.2 POST注解:用于发送一个POST请求。
POST注解一般必须添加相对路径或绝对路径或者全路径, 如果不想在POST注解后添加请求路径,则可以在方法的第一个参数中用@Url注解添加请求路径
1.3 DELETE注解:用于发送一个DELETE请求。
DELETE注解一般必须添加相对路径或绝对路径或者全路径,如果不想在DELETE注解后添加请求路径,则可以在方法的第一个参数中用@Url注解添加请求路径
1.4 HEAD注解: 用于发送一个HEAD请求。
HEAD注解一般必须添加相对路径或绝对路径或者全路径,如果不想在HEAD注解后添加请求路径,则可以在方法的第一个参数中用@Url注解添加请求路径
1.5 PUT注解:用于发送一个PUT请求。
PUT注解一般必须添加相对路径或绝对路径或者全路径,如果不想在PUT注解后添加请求路径,则可以在方法的第一个参数中用@Url注解添加请求路径
1.6 PATCH注解: 用于发送一个PATCH请求。PATCH注解一般必须添加相对路径或绝对路径或者全路径,
如果不想在PATCH注解后添加请求路径,则可以在方法的第一个参数中用@Url注解,添加请求路径。
1.7 OPTIONS注解:用于发送一个OPTIONS请求。
OPTIONS注解一般必须添加相对路径或绝对路径或者全路径,如果不想在OPTIONS注解后添加请求路径,则可以在方法的第一个参数中用@Url注解添加请求路径
1.8 HTTP注解: 作用于方法,用于发送一个自定义的HTTP请求。
//自定义HTTP请求的标准样式
interface Service {
@HTTP(method = "CUSTOM", path = "custom/endpoint/")
Call<ResponseBody> customEndpoint();
}
//发送一个DELETE请求
interface Service {
@HTTP(method = "DELETE", path = "remove/", hasBody = true)
Call<ResponseBody> deleteObject(@Body RequestBody object);
}
二.标记注解:
2.2 Multipart注解: 作用于方法。 使用该注解,表示请求体是多部分的。 每一部分作为一个参数,且用Part注解声明
@Multipart
@POST("/upload")
Call<ResponseBody> upload( @Part("file") RequestBody file, @PartMap Map<String, RequestBody> params);
2.2 FormUrlEncoded注解: 用于修饰Field注解和FieldMap注解。
使用该注解,表示请求正文将使用表单网址编码。字段应该声明为参数,并用@Field注释或FieldMap注释。
使用FormUrlEncoded注解的请求将具”application / x-www-form-urlencoded” MIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码.
2.3 Streaming注解:作用于方法。
处理返回Response的方法的响应体,即没有将body()转换为byte []。
三.参数注解:
3.0 HeaderMap注解:
作用于方法的参数,用于添加请求头。
以map的方式添加多个请求头,map中的key为请求头的名称,value为请求头的值,且value使用String.valueOf()统一转换为String类型,
map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
@GET("/search")
void list(@HeaderMap Map<String, String> headers); //map
Map<String,String> headers = new HashMap()<>;
headers.put("Accept","text/plain");
headers.put("Accept-Charset", "utf-8");
@GET("/") ...
3.1 Body注解:作用于方法的参数(实体类的参数)使用该注解定义的参数不可为null。
当你发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化后的结果 直接作为请求体发送出去.
class Repo {
final String owner;
final String name;
Repo(String owner, String name) {
this.owner = owner; this.name = name;
}
}
//接口
interface Service {
@POST("/")
Call<ResponseBody> sendNormal(@Body Repo repo);
3.2 Part注解:作用于方法的参数,用于定义Multipart请求的每个part。
使用该注解定义的参数,参数值可以为空,为空时,则忽略
使用该注解定义的参数类型有以下3种方式可选:
1, 如果类型是okhttp3.MultipartBody.Part,内容将被直接使用。 省略part中的名称,即 @Part MultipartBody.Part part
2, 如果类型是RequestBody,那么该值将直接与其内容类型一起使用。 在注释中提供part名称(例如,@Part(“foo”)RequestBody foo)。
3, 其他对象类型将通过使用转换器转换为适当的格式。 在注释中提供part名称(例如,@Part(“foo”)Image photo)。
示例:
@Multipart
@POST("/")
Call<ResponseBody> example( @Part("description") String description, @Part(value = "image", encoding = "8-bit") RequestBody image);
s
3.3 PartMap注解:作用于方法的参数,以map的方式定义Multipart请求的每个part。map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常。
使用该注解定义的参数类型有以下2种方式可选:
1, 如果类型是RequestBody,那么该值将直接与其内容类型一起使用。
2, 其他对象类型将通过使用转换器转换为适当的格式。
@Multipart
@POST("/upload")
Call<ResponseBody> upload( @Part("file") RequestBody file, @PartMap Map<String, RequestBody> params);
3.4 Field注解: 作用于方法的参数,用于发送一个表单请求。
用String.valueOf()把参数值转换为String,然后进行URL编码,当参数值为null值时,会自动忽略。
如果传入的是一个List或array,则为每一个非空的item拼接一个键值对,每一个键值对中的键是相同的,值就是非空item的值。
如: name=张三&name=李四&name=王五,另外, 如果item的值有空格,在拼接时会自动忽略,
例如某个item的值为:张 三,则拼接后为name=张三.
//普通参数
@FormUrlEncoded
@POST("/")
Call<ResponseBody> example(@Field("name") String name,@Field("occupation") String occupation);
//固定或可变数组
@FormUrlEncoded
@POST("/list")
Call<ResponseBody> example(@Field("name") String... names);
3.5 FieldMap注解:作用于方法的参数,用于发送一个表单请求。
map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常。
@FormUrlEncoded
@POST("/things")
Call<ResponseBody> things(@FieldMap Map<String, String> fields);
3.6 Query注解:
作用于方法的参数,用于添加查询参数,即请求参数。参数值通过String.valueOf()转换为String并进行URL编码。
使用该注解定义的参数,参数值可以为空,为空时,忽略该值;
当传入一个List或array时,为每个非空item拼接请求键值对,所有的键是统一的,如: name=张三&name=李四&name=王五.
示例:
@GET("/list")
Call<ResponseBody> list(@Query("page") int page);
@GET("/list")
Call<ResponseBody> list(@Query("category") String category);//传入一个数组
@GET("/list")
Call<ResponseBody> list(@Query("category") String... categories);//不进行URL编码
@GET("/search")
Call<ResponseBody> list(@Query(value="foo", encoded=true) String foo);
3.7 QueryMap注解:
作用于方法的参数,以map的形式添加查询参数,即请求参数。参数的键和值都通过String.valueOf()转换为String格式。
map的键和值默认进行URL编码。map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
示例:
//使用默认URL编码
@GET("/search")
Call<ResponseBody> list(@QueryMap Map<String, String> filters);//不使用默认URL编码
@GET("/search")
Call<ResponseBody> list(@QueryMap(encoded=true) Map<String, String> filters);
四.其他注解:
4.1 Path注解: 作用于方法的参数。在URL路径段中替换指定的参数值。使用String.valueOf()和URL编码将值转换为字符串。
使用该注解定义的参数的值不可为空,参数值默认使用URL编码。
//标准使用方式,默认使用URL编码
@GET("/image/{id}")
Call<ResponseBody> example(@Path("id") int id);//默认使用URL编码
@GET("/user/{name}")
Call<ResponseBody> encoded(@Path("name") String name);
//不使用URL编码
@GET("/user/{name}")
Call<ResponseBody> notEncoded(@Path(value="name", encoded=true) String name);
4.2 Header注解:
作用于方法的参数,用于添加请求头。
使用该注解定义的请求头可以为空,当为空时,会自动忽略,当传入一个List或array时,为拼接每个非空的item的值到请求头中.
具有相同名称的请求头不会相互覆盖,而是会照样添加到请求头中
@GET("/")
Call<ResponseBody> foo(@Header("Accept-Language") String lang);
4.3 Headers注解: 作用于方法, 用于添加一个或多个请求头。
具有相同名称的请求头不会相互覆盖,而是会照样添加到请求头中
//添加一个请求头
@Headers("Cache-Control: max-age=640000")
@GET("/") ...
//添加多个请求头
@Headers({ "X-Foo: Bar", "X-Ping: Pong" })
4.4 Url注解: 作用于方法参数, 用于添加请求的接口地址。
@GET
Call<ResponseBody> list(@Url String url);
2.android retrofit 请求参数格式RequestBody的方法
以前都是使用
Observable<ResponseBody> login(@HeaderMap Map<String, String> headers, @QueryMap Map<String, String> map);
@QueryMap方式,将参数放入map中传输的
现改用RequestBody的方式
Observable<ResponseBody> login(@HeaderMap Map<String, String> headers, @Body RequestBody requestBody);
那么如何转换呢,很简单,只需要把以前的map封装一下即可得到RequestBody作为参数了
/**
* 将map数据转换为 普通的 json RequestBody
* @param map 以前的请求参数
* @return
*/
public static RequestBody convertMapToBody(Map<?,?> map) {
return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), new JSONObject(map).toString());
}
/**
* 将map数据转换为图片,文件类型的 RequestBody
* @param map 以前的请求参数
* @return 待测试
*/
public static RequestBody convertMapToMediaBody(Map<?,?> map) {
return RequestBody.create(MediaType.parse("multipart/form-data; charset=utf-8"), new JSONObject(map).toString());
}
1.0 retrofit模型:
POJO或模型实体类 : 从服务器获取的JSON数据将被填充到这种类的实例中。
接口 : 我们需要创建一个接口来管理像GET,POST...等请求的URL,这是一个服务类。
RestAdapter类 : 这是一个REST客户端(RestClient)类,retrofit中默认用的是Gson来解析JSON数据,你也可以设置自己的JSON解析器
使用:创建一个Retrofit 对象(核心用法一)
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())//解析方法
//这里建议:- Base URL: 总是以/结尾;- @Url: 不要以/开头
.baseUrl("http://102.10.10.132/api/")
.build();
接口申明(核心用法二):按照一个http请求对应的写法。
http://102.10.10.132/api/News/1
http://102.10.10.132/api/News/{资讯id}
public interface NewsService {
/**
* 根据newsid获取对应的资讯数据
* 如果不需要转换成Json数据,可以用了ResponseBody;
* @param newsId
* @return call
*/
@GET("News/{newsId}")
Call<News> getNews(@Path("newsId") String newsId);
}或
http://102.10.10.132/api/News/1/类型1
http://102.10.10.132/api/News/{资讯id}/{类型}
@GET("News/{newsId}/{type}")
Call<NewsBean> getItem(@Path("newsId") String newsId, @Path("type") String type);
样式3(参数在URL问号之后)
http://102.10.10.132/api/News?newsId=1
http://102.10.10.132/api/News?newsId={资讯id}
@GET("News")
Call<NewsBean> getItem(@Query("newsId") String newsId);
或
http://102.10.10.132/api/News?newsId=1&type=类型1
http://102.10.10.132/api/News?newsId={资讯id}&type={类型}
@GET("News")
Call<NewsBean> getItem(@Query("newsId") String newsId, @Query("type") String type);
样式4(多个参数在URL问号之后,且个数不确定)
http://102.10.10.132/api/News?newsId=1&type=类型1...
http://102.10.10.132/api/News?newsId={资讯id}&type={类型}...
@GET("News")
Call<NewsBean> getItem(@QueryMap Map<String, String> map);
也可以
@GET("News")
Call<NewsBean> getItem(@Query("newsId") String newsId, @QueryMap Map<String, String> map);
POST:
样式1(需要补全URL,post的数据只有一条reason)
http://102.10.10.132/api/Comments/1
http://102.10.10.132/api/Comments/{newsId}
@FormUrlEncoded
@POST("Comments/{newsId}")
Call<Comment> reportComment(@Path("newsId") String commentId, @Field("reason")String reason);
样式2(需要补全URL,问号后加入access_token,post的数据只有一条reason)
http://102.10.10.132/api/Comments/1?access_token=1234123
http://102.10.10.132/api/Comments/{newsId}?access_token={access_token}
@FormUrlEncoded
@POST("Comments/{newsId}")
Call<Comment> reportComment(@Path("newsId") String commentId,@Query("access_token") String access_token, @Field("reason") String reason);
样式3(需要补全URL,问号后加入access_token,post一个body(对象))
http://102.10.10.132/api/Comments/1?access_token=1234123
http://102.10.10.132/api/Comments/{newsId}?access_token={access_token}
@POST("Comments/{newsId}")
Call<Comment> reportComment(@Path("newsId") String commentId,
@Query("access_token") String access_token, @Body CommentBean bean);
上传文件的
public interface FileUploadService {
@Multipart
@POST("upload")
Call<ResponseBody> upload(@Part("description") RequestBody description,@Part MultipartBody.Part file);
如果你需要上传文件,和我们前面的做法类似,定义一个接口方法,需要注意的是,这个方法不再有 @FormUrlEncoded 这个注解,
而换成了@Multipart,后面只需要在参数中增加 Part 就可以了。
//构建要上传的文件
File file = new File(filename);
RequestBody requestFile = RequestBody.create(MediaType.parse("application/otcet-stream"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("aFile", file.getName(), requestFile);
String descriptionString = "This is a description";
RequestBody description =RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);
DELETE
样式1(需要补全URL):
http://102.10.10.132/api/Comments/1
http://102.10.10.132/api/Comments/{newsId}{access_token}
@DELETE("Comments/{commentId}")
Call<ResponseBody> deleteNewsCommentFromAccount(@Path("commentId") String commentId);
样式2(需要补全URL,问号后加入access_token)
http://102.10.10.132/api/Comments/1?access_token=1234123
http://102.10.10.132/api/Comments/{newsId}?access_token={access_token}
@DELETE("Comments/{commentId}")
Call<ResponseBody> deleteNewsCommentFromAccount(
@Path("accountId") String accountId,
@Query("access_token") String access_token);
PUT(这个请求很少用到,例子就写一个)
http://102.10.10.132/api/Accounts/1
http://102.10.10.132/api/Accounts/{accountId}
@PUT("Accounts/{accountId}")
Call<ExtrasBean> updateExtras(
@Path("accountId") String accountId,@Query("access_token") String access_token,@Body ExtrasBean bean);
创建访问API的请求(核心用法三)
NewsService serviceApi = retrofit.create(NewsService .class);
Call<News> call = serviceApi.getNews("123456");
同步调用(核心用法四)
News news = call.execute();
异步调用(核心用法五)
call.enqueue(new Callback<News>(){
@Override
public void onResponse(Response<News> response) {
//成功返回数据后在这里处理,使用response.body();获取得到的结果
News news = response.body();
}
@Override
public void onFailure(Throwable t) {
//请求失败在这里处理
}
});
取消请求(核心用法六)
call.cancel();
完成以上步骤就可以实现一个简单的网络请求了。