1.Retrofit介绍
retrofit是在okhttp的基础上进行封装的,网络请求是通过okhttp实现的。
具体使用细节可查看retrofit官网
2.Retrofit注解
Retrofit通过注解的方式,进行网络请求。根据功能分类,注解可以分为:
2.1请求方法类:
编号 | 名称 |
1 | GET |
2 | POST |
3 | PUT |
4 | DELETE |
5 | PATCH |
6 | HEAD |
7 | OPTIONS |
8 | HTTP |
1.GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、HTTP(编号8,可以代替上面7个)
2.分别对应 HTTP 的请求方法;(最后的HTTP除除外)
3.接收一个字符串表示接口 path ,与 baseUrl 组成完整的 Url;
4.可以不指定,结合 @Url 注解使用;
5.url 中可以使用变量,如 {id} ,并使用 @Path(“id”) 注解为 {id} 提供值;
对于编号8,HTTP的使用方法如下:
有 3 个属性:method、path、hasBody
public interface RetrofitService
{
@HTTP(method = "get", path = "new{id}", hasBody = false)
Call<ResponseBody> getNew(@Path("id") int id);
}
2.1.1:GET
- 一个简单的Get请求
public interface RetrofitService
{
// http://192.168.1.102/api/News
@GET("News")
Call<NewsBean> getItem();
}
- URL中有参数的Get请求
public interface RetrofitService
{
// http://192.168.1.102/api/News/1
// http://192.168.1.102/api/News/{资讯id}
@GET("News/{newsId}")
Call<NewsBean> getItem(@Path("newsId") String newsId);
// http://192.168.1.102/api/News/1/类型1
// http://192.168.1.102/api/News/{资讯id}/{类型}
@GET("News/{newsId}/{type}")
Call<NewsBean> getItem(@Path("newsId") String newsId, @Path("type") String type);
}
- 参数在URL问号之后的Get请求
public interface RetrofitService
{
// http://192.168.1.102/api/News?newsId=1
// http://192.168.1.102/api/News?newsId={资讯id}
@GET("News")
Call<NewsBean> getItem(@Query("newsId") String newsId);
// http://192.168.1.102/api/News?newsId=1&type=类型1
// http://192.168.1.102/api/News?newsId={资讯id}&type={类型}
@GET("News")
Call<NewsBean> getItem(@Query("newsId") String newsId, @Query("type") String type);
}
- 多个参数在URL问号之后,且个数不确定的Get请求
public interface RetrofitService
{
// http://192.168.1.102/api/News?newsId=1&type=类型1...
// http://192.168.1.102/api/News?newsId={资讯id}&type={类型}...
@GET("News")
Call<NewsBean> getItem(@QueryMap Map<String, String> map);
// http://192.168.1.102/api/News?newsId=1&type=类型1...
// http://192.168.1.102/api/News?newsId={资讯id}&type={类型}...
@GET("News")
Call<NewsBean> getItem(@Query("newsId") String newsId, @QueryMap Map<String, String> map);
}
2.1.2: POST
- 需要补全URL,post的数据只有一条reason的Post请求
public interface RetrofitService
{
// http://192.168.1.102/api/Comments/1
// http://192.168.1.102/api/Comments/{newsId}
@FormUrlEncoded
@POST("Comments/{newsId}")
Call<Comment> reportComment(@Path("newsId") String commentId, @Field("reason") String reason);
}
- 需要补全URL,问号后加入access_token,post的数据只有一条reason的Post请求
public interface RetrofitService
{
// http://192.168.1.102/api/Comments/1?access_token=1234123
// http://192.168.1.102/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);
}
- 需要补全URL,问号后加入access_token,post一个body(对象)的Post请求
public interface RetrofitService
{
// http://192.168.1.102/api/Comments/1?access_token=1234123
// http://192.168.1.102/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);
}
2.1.3:DELETE
- 需要补全URL的Delete请求
public interface RetrofitService
{
// http://192.168.1.102/api/Comments/1
// http://192.168.1.102/api/Comments/{commentId}
@DELETE("Comments/{commentId}")
Call<ResponseBody> deleteNewsCommentFromAccount(@Path("commentId") String commentId);
}
- 需要补全URL,问号后加入access_token的Delete请求
public interface RetrofitService
{
// http://192.168.1.102/api/Comments/1?access_token=1234123
// http://192.168.1.102/api/Comments/{commentId}?access_token={access_token}
@DELETE("Comments/{commentId}")
Call<ResponseBody> deleteNewsCommentFromAccount(@Path("commentId") String commentId, @Query("access_token") String access_token);
}
- 带有body的Delete请求
public interface RetrofitService
{
// http://192.168.1.102/api/Comments
@HTTP(method = "DELETE",path = "Comments",hasBody = true)
Call<ResponseBody> deleteCommont(@Body CommentBody body);
}
2.1.4:PUT
public interface RetrofitService
{
// http://192.168.1.102/api/Accounts/1
// http://192.168.1.102/api/Accounts/{accountId}
@PUT("Accounts/{accountId}")
Call<ExtrasBean> updateExtras(@Path("accountId") String accountId, @Query("access_token") String access_token, @Body ExtrasBean bean);
}
2.2标记类:
2.2.1:FormUrlEncoded
FormUrlEncoded:
1.用于修饰Field注解和FieldMap注解
2.使用该注解,表示请求正文将使用表单网址编码。字段应该声明为参数,并用@Field注释或FieldMap注释。使用FormUrlEncoded注解的请求将具”application / x-www-form-urlencoded” MIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码.
2.2.2:Multipart
Multipart:
1.作用于方法
2.使用该注解,表示请求体是多部分的。 每一部分作为一个参数,且用Part注解声明
上传单个文件
接口
public interface RetrofitService
{
@Multipart
@POST("/uploadImgs")
Call<ResponseServer<List<PicResultData>>> uploadSingleImg(@Part("description") RequestBody description, @Part MultipartBody.Part file);
}
实现
public void uploadImg(Object pcObj, String fileUrl)
{
File file = new File(fileUrl);
// 创建 RequestBody,用于封装构建RequestBody
// RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpg"), file);
// MultipartBody.Part 和后端约定好Key,这里的partName是用file
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
// 添加描述
String descriptionString = "hello, 这是文件描述";
RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);
// 执行请求
serviceApi.uploadSingleImg(description, body).enqueue(new BaseViewModel.HttpRequestCallback<List<PicResultData>>()
{
@Override
public void onSuccess(List<PicResultData> result)
{
super.onSuccess(result);
}
@Override
public void onFailure(int status, String message)
{
super.onFailure(status, message);
}
});
}
上传多个文件
接口
public interface RetrofitService
{
@Multipart
@POST("/uploadImgs")
Call<ResponseServer<List<PicResultData>>> uploadMultiImgs(@PartMap Map<String, RequestBody> maps);
}
实现
public void uploadImgs(Object pcObj, List<String> imgStrs)
{
Map<String, RequestBody> map = new HashMap<>();
for (String imgUrl : imgStrs)
{
File file = new File(imgUrl);
// create RequestBody instance from file
// RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpg"), file);
// 注意:file就是与服务器对应的key,后面filename是服务器得到的文件名
map.put("file\"; filename=\"" + file.getName(), requestFile);
}
// 执行请求
serviceApi.uploadMultiImgs(map).enqueue(new BaseViewModel.HttpRequestCallback<List<PicResultData>>()
{
@Override
public void onSuccess(List<PicResultData> result)
{
super.onSuccess(result);
}
@Override
public void onFailure(int status, String message)
{
super.onFailure(status, message);
}
});
}
2.2.3:Streaming
Streaming:
1.作用于方法
2.处理返回Response的方法的响应体,即没有将body()转换为byte []
3.响应体的数据用流的形式返回
4.未使用该注解,默认会把数据全部载入内存,之后通过流获取数据也是读取内存中数据,所以返回数据较大时,需要使用该注解
public interface RetrofitService
{
@Streaming
@GET("/update_apk")
Call<ResponseServer<News>> downloadFile(@Url String fileUrl);
}
2.3参数类:
2.3.1:Headers
Headers:
1.作用于方法,用于添加一个或多个请求头
2.使用 @Headers 注解设置固定的请求头,所有请求头不会相互覆盖,即使名字相同
定义Headers
public interface RetrofitService
{
// 添加一个请求体
@Headers("Cache-Control: max-age=640000")
@GET("/widget/list")
Call<List<Widget>> widgetList();
// 添加多个请求体
@Headers({"Accept: application/vnd.yourapi.v1.full+json", "User-Agent: Your-App-Name"})
@GET("/users/{username}")
Call<User> getUser(@Path("username") String username)
}
使用Headers,需要使用拦截器
private RetrofitHelper()
{
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new Interceptor()
{
@Override
public Response intercept(Chain chain) throws IOException
{
Request original = chain.request();
Request request = original.newBuilder()
.header("User-Agent", "Your-App-Name")
.header("Accept", "application/vnd.yourapi.v1.full+json")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
});
OkHttpClient client = builder.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL_WEATHER)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
}
2.3.2:Header
Header:
1.作用于方法参数(形参)
2.使用 @Header 注解动态更新请求头,匹配的参数必须提供给 @Header ,若参数值为 null ,这个头会被省略;当传入一个List或array时,为拼接每个非空的item的值到请求头中
3.具有相同名称的请求头不会相互覆盖,而是会照样添加到请求头中
public interface RetrofitService
{
@GET("/tasks")
Call<List<Task>> getTasks(@Header("Content-Range") String contentRange);
}
2.3.3:Body
Body:
1.作用于方法参数(形参)
2.使用该注解定义的参数不可为null
3.当发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化后的结果直接作为请求体发送出去;如果提交的是一个Map,那么作用相当于 @Field
public interface RetrofitService
{
@GET("/users/new")
Call<ResponseBody> createUser(@Body RequestBody requestBody);
}
2.3.4:Field
Field:
1.作用于方法参数(形参)
2.发送 Post请求 时提交请求的表单字段,与 @FormUrlEncoded 注解配合使用
3.用String.valueOf()把参数值转换为String,然后进行URL编码,当参数值为null值时,会自动忽略,如果传入的是一个List或array,则为每一个非空的item拼接一个键值对,每一个键值对中的键是相同的,值就是非空item的值,如: name=张三&name=李四&name=王五,另外,如果item的值有空格,在拼接时会自动忽略,例如某个item的值为:张 三,则拼接后为name=张三.
public interface RetrofitService
{
// 普通参数
@FormUrlEncoded
@POST("/users/addphoto")
Call<List<Repo>> listRepos(@Field("time") long time);
// 固定或可变数组
@FormUrlEncoded
@POST("/users/addphoto")
Call<List<Repo>> listRepos(@Field("name") String... names);
}
2.3.5:FieldMap
FieldMap:
1.作用于方法参数(形参)
2.表单字段,与 Field、FormUrlEncoded 配合;接受 Map<String, String> 类型,非 String 类型会调用 toString() 方法
3.map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
public interface RetrofitService
{
@FormUrlEncoded
@POST("/things")
Call<List<Repo>> things(@FieldMap Map<String, String> params);
}
2.3.6:Part
Part:
1.作用于方法参数(形参)
2.post请求时,提交请求的表单字段,与 @ Multipart注解 配合
3.使用该注解定义的参数,参数值可以为空,为空时,则忽略
4.使用该注解定义的参数类型有以下3种方式可选:
a, 如果类型是okhttp3.MultipartBody.Part,内容将被直接使用。 省略part中的名称,即 @Part MultipartBody.Part part
b, 如果类型是RequestBody,那么该值将直接与其内容类型一起使用。 在注释中提供part名称(例如,@Part(“foo”)RequestBody foo)
c, 其他对象类型将通过使用转换器转换为适当的格式。 在注释中提供part名称(例如,@Part(“foo”)Image photo)
上传单个文件
接口
public interface RetrofitService
{
@Multipart
@POST("/uploadImgs")
Call<ResponseServer<List<PicResultData>>> uploadSingleImg(@Part("description") RequestBody description, @Part MultipartBody.Part file);
}
实现
public void uploadImg(Object pcObj, String fileUrl)
{
File file = new File(fileUrl);
// 创建 RequestBody,用于封装构建RequestBody
// RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpg"), file);
// MultipartBody.Part 和后端约定好Key,这里的partName是用file
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
// 添加描述
String descriptionString = "hello, 这是文件描述";
RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);
// 执行请求
serviceApi.uploadSingleImg(description, body).enqueue(new BaseViewModel.HttpRequestCallback<List<PicResultData>>()
{
@Override
public void onSuccess(List<PicResultData> result)
{
super.onSuccess(result);
}
@Override
public void onFailure(int status, String message)
{
super.onFailure(status, message);
}
});
}
2.3.7:PartMap
PartMap:
1.作用于方法参数(形参)
2.post请求时,提交请求的表单字段,与 @ Multipart注解 配合
3.map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
4.使用该注解定义的参数类型有以下2种方式可选:
a, 如果类型是RequestBody,那么该值将直接与其内容类型一起使用
b, 其他对象类型将通过使用转换器转换为适当的格式。
5.携带的参数类型更加丰富,包括数据流,所以适用于 有文件上传 的场景
上传多个文件
接口
public interface RetrofitService
{
@Multipart
@POST("/uploadImgs")
Call<ResponseServer<List<PicResultData>>> uploadMultiImgs(@PartMap Map<String, RequestBody> maps);
}
实现
public void uploadImgs(Object pcObj, List<String> imgStrs)
{
Map<String, RequestBody> map = new HashMap<>();
for (String imgUrl : imgStrs)
{
File file = new File(imgUrl);
// create RequestBody instance from file
// RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpg"), file);
// 注意:file就是与服务器对应的key,后面filename是服务器得到的文件名
map.put("file\"; filename=\"" + file.getName(), requestFile);
}
// 执行请求
serviceApi.uploadMultiImgs(map).enqueue(new BaseViewModel.HttpRequestCallback<List<PicResultData>>()
{
@Override
public void onSuccess(List<PicResultData> result)
{
super.onSuccess(result);
}
@Override
public void onFailure(int status, String message)
{
super.onFailure(status, message);
}
});
}
2.3.8:Path
Path:
1.作用于方法参数(形参)
2.在URL路径段中替换指定的参数值。
3.使用String.valueOf()和URL编码将值转换为字符串。
4.使用该注解定义的参数的值不可为空参数值默认使用URL编码
public interface RetrofitService
{
@GET("/data/sk/{cityId}.html")
Call<ResponseBody> getWeatherByCityId(@Path("cityId") String cityId);
}
2.3.9:Query
Query:
1.作用于方法参数(形参)
2.用于添加查询参数,即请求参数(Query = Url 中 ‘?’ 后面的 key-value)
3.参数值通过String.valueOf()转换为String并进行URL编码
4.使用该注解定义的参数,参数值可以为空,为空时,忽略该值,当传入一个List或array时,为每个非空item拼接请求键值对,所有的键是统一的,如: name=张三&name=李四&name=王五.
public interface RetrofitService
{
// 普通参数
@GET("/list")
Call<ResponseBody> list(@Query("catrgory") String catrgory);
// 传入一个数组
@GET("/list")
Call<ResponseBody> list(@Query("catrgory") String... catrgory);
// 不进行URL编码
@GET("/search")
Call<ResponseBody> list(@Query(value="foo", encoded=true) String foo);
}
2.3.10:QueryMap
QueryMap:
1.作用于方法参数(形参)
2.以map的形式添加查询参数,即请求参数
3.参数的键和值都通过String.valueOf()转换为String格式
4.map的键和值默认进行URL编码map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
public interface RetrofitService
{
// 使用默认URL编码
@GET("/search")
Call<ResponseBody> list(@QueryMap Map<String, String> filters);
// 不使用默认URL编码
@GET("/search")
Call<ResponseBody> list(@QueryMap(encoded=true) Map<String, String> filters);
}
2.3.11:Url
Url:
1.作用于方法参数(形参)
2.直接传入一个请求的 URL变量 用于URL设置
public interface RetrofitService
{
@GET
Call<ResponseBody> urlAndQuery(@Url String url, @Query("showAll") boolean showAll);
}
3.参数注解小结:
1.Map 用来组合复杂的参数;
2.Query、QueryMap 与 Field、FieldMap 功能一样,生成的数据形式一样;
Query、QueryMap 的数据体现在 Url 上;
Field、FieldMap 的数据是请求体;
3.{占位符}和 PATH 尽量只用在URL的 path 部分,url 中的参数使用 Query、QueryMap 代替,保证接口的简洁;
4.Query、Field、Part 支持数组和实现了 Iterable 接口的类型, 如 List、Set等,方便向后台传递数组。
4.注意事项:
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.”);