![7fc47062a4d86bfbe96303d6b9df34f7.png](https://i-blog.csdnimg.cn/blog_migrate/ec5a4de78b4051b10958f51dee0a70af.jpeg)
背景图来源:livery情景专属小红图片
解决Okhttp的Response#body()#string()后Response返回体为空问题,在使用livery可以很容易的避免返回空null值无法解析的问题,这一点后面《几行代码带你实现网络请求》中会统一处理
这次废话少说,直接开始Retrofit注解参数介绍,
Retrofit网络请求参数详解@Path、@Query、@QueryMap(含文件上传下载)
Retrofit通过注解的方式,进行网络请求。根据功能分类,注解可以分为:
![2a3bdc9ed9ac806bbbd1b64aa84071f6.png](https://i-blog.csdnimg.cn/blog_migrate/da0c25ae764c77f88b320ec74c7f6aed.jpeg)
为了方便大家操作测试和下篇《拥抱Livery2:几行代码带你实现网络请求(含上传下载)》内容介绍,请直接依赖livery库(包含了最新RxJava+Retrofit)
dependencies {
implementation'com.sunsta.livery:livery:1.1.11'
}
一:请求方法类:
1GET
2POST
3PUT
4DELETE
5PATCH
6HEAD
7OPTIONS
8HTTP
1.GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、
HTTP 可以代替上面7个
2.分别对应 HTTP 的请求方法;(最后的HTTP除除外)
3.接收一个字符串表示接口 path ,与 baseUrl 组成完整的 Url;
4.可以不指定,结合@Url 注解使用;
5.url 中可以使用变量,如{id} ,并使用@Path("id") 注解为{id}提供值;
6.对于编号8,HTTP的使用方法如下:
有 3个属性:method、path、hasBody
public interface RetrofitService {
@HTTP(method = "get", path = "new{id}", hasBody = false)
Call<ResponseBody> getNew(@Path("id") int id);
}
下面围绕以上8个请求方法进行一一介绍
版权声明CopyRight:
本内容作者:sunst,转载或引用请标明出处 ,违者追究法律责任!!!
初始化Retrofit
String BASE_URL = "http://102.10.93.69/api/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.build();
1.GET
样式1(一个简单的get请求)
http://102.10.93.69/api/News
@GET("News")
Call<NewsBean> getItem();
样式2(URL中有参数)
http://102.10.93.69/api/News/1http://102.10.93.69/api/News/{资讯id}
@GET("News/{newsId}")
Call<NewsBean> getItem(@Path("newsId") String newsId);
或http://102.10.93.69/api/News/1/类型1http://102.10.93.69/api/News/{资讯id}/{类型}
@GET("News/{newsId}/{type}")
Call<NewsBean> getItem(@Path("newsId") String newsId, @Path("type") String type);
样式3(参数在URL问号之后)
http://102.10.93.69/api/News?newsId=1http://102.10.93.69/api/News?newsId={资讯id}
@GET("News")
Call<NewsBean> getItem(@Query("newsId") String newsId);
或http://102.10.93.69/api/News?newsId=1&type=类型1http://102.10.93.69/api/News?newsId={资讯id}&type={类型}
@GET("News")
Call<NewsBean> getItem(@Query("newsId") String newsId, @Query("type") String type);
样式4(多个参数在URL问号之后,且个数不确定)
http://102.10.93.69/api/News?newsId=1&type=类型1...http://102.10.93.69/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);
GET下载文件
下载文件其实就一个普通的GET请求,只不过我们处理好IO操作,将response.body()进行保存为自己想要的位置或者处理(想知道,文件如何上传,请耐心往后看哟)
@GET("u=107188706,3427188039&fm=27&gp=0.jpg")
Call<ResponseBody> downloadFile();
实现:
Call<ResponseBody> call = RetrofitHelper.getDownloadApi().downloadFile();
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
boolean writtenToDisk = writeResponseBodyToDisk(response.body());
btnDownload.setText(writtenToDisk ? "success" : "false");
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Toast.makeText(TestUploadFileActivity.this, "fail", Toast.LENGTH_SHORT).show();
}
});
writeResponseBodyToDisk()方法介绍,注意读写申请文件权限
private boolean writeResponseBodyToDisk(ResponseBody body) {
try {
File futureStudioIconFile = new File(Environment.getExternalStorageDirectory() + File.separator + "taylor.png");
Log.d(TAG, "writeResponseBodyToDisk: " + Environment.getExternalStorageDirectory().getAbsolutePath());
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
}
outputStream.flush();
return true;
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
,本神再次提醒大家特别注意:
/storage/emulated/0/data/cache/test.apk: open failed: ENOENT (No such file or directory)
导致这个问题的原因就两个,不存在,没权限。
动态获取到权限,一定要在AndroidManifest.xml中声明读写权限;然后动态获取到读或者写权限,就能获取到读写两者的权限,但是你不能只声明其中一个权限,两个都需要声明,才能两个都使用。
2.POST
样式1(需要补全URL,post的数据只有一条reason)
http://102.10.93.69/api/Comments/1http://102.10.93.69/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.93.69/api/Comments/1?access_token=1234123http://102.10.93.69/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.93.69/api/Comments/1?access_token=1234123http://102.10.93.69/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);
3.PUT(这个请求很少用到,例子就写一个)
http://102.10.93.69/api/Accounts/1http://102.10.93.69/api/Accounts/{accountId}
@PUT("Accounts/{accountId}")
Call<ExtrasBean> updateExtras(
@Path("accountId") String accountId,
@Query("access_token") String access_token,
@Body ExtrasBean bean);
4.DELETE
样式1(需要补全URL)
http://102.10.93.69/api/Comments/1http://102.10.93.69/api/Comments/{commentId}
@DELETE("Comments/{commentId}")
Call<ResponseBody> deleteNewsCommentFromAccount(
@Path("commentId") String commentId);
样式2(需要补全URL,问号后加入access_token)
http://102.10.93.69/api/Comments/1?access_token=1234123http://102.10.93.69/api/Comments/{commentId}?access_token={access_token}
@DELETE("Comments/{commentId}")
Call<ResponseBody> deleteNewsCommentFromAccount(
@Path("commentId") String commentId,
@Query("access_token") String access_token);
样式3(带有body)
http://102.10.93.69/api/Comments
@HTTP(method = "DELETE",path = "Comments",hasBody = true)
Call<ResponseBody> deleteCommont(
@Body CommentBody body
);
CommentBody
:需要提交的内容,与Post
中的Body
相同
5.PATCH(这个请求不常用到,,例子还是写一个)
http://102.10.93.69/api/Gists/0069http://102.10.93.69/api/Gists/{id}
@PATCH("Gists/{id}")
Call<ResponseBody> updateCommentsPatch(@Path("id") String id, @Body Gist gist);
需要说明一下的是@PUT也是这么使用的,是对put请求的补充,用于更新局部资源。
retrofit中使用@PATCH时,如果发现参数一直找不到对应的注解,那么一般是注解使用错误,我们需要使用的@Body注解(如上参数使用@Path和@Body注解)
6.HEAD
暂略
7.OPTIONS
暂略
8.HTTP
本文前面开头已经介绍
二:标记类:
1:FormUrlEncoded
FormUrlEncoded不能用于Get请求,不要乱用
1.用于修饰Field注解和FieldMap注解
2.使用该注解,表示请求正文将使用表单网址编码。字段应该声明为参数,并用@Field注释或FieldMap注释。使用FormUrlEncoded注解的请求将具”application / x-www-form-urlencoded” MIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码.
注意:FormUrlEncoded用于修饰Field注解和FieldMap注解,将会自动将请求参数的类型调整为application/x-www-form-urlencoded
2:Multipart
Multipart:
1.作用于方法
2.使用该注解,表示请求体是多部分的。 每一部分作为一个参数,且用Part注解声明
(1)上传单个文件
接口
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)上传多个文件
接口
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);
}
});
}
3:Streaming
1.作用于方法
2.处理返回Response的方法的响应体,即没有将body()转换为byte []
3.响应体的数据用流的形式返回
4.未使用该注解,默认会把数据全部载入内存,之后通过流获取数据也是读取内存中数据,所以返回数据较大时,需要使用该注解
public interface RetrofitService {
@Streaming
@GET("/update_apk")
Call<ResponseServer<News>> downloadFile(@Url String fileUrl);
}
三:请求参数
1: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({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: RetrofitBean-Sample-App",
"name:ljd"
})
@GET("repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributorsAndAddHeader(@Path("owner") String owner,@Path("repo") String repo);
方式三:等同于下面Header注解
@GET("user")
Call getUser(@Header("Authorization") String authorization);
等同于:
@Headers("Authorization: authorization")//这里authorization就是上面方法里传进来变量的值
@GET("widget/list")
Call getUser()
使用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:Header
1.作用于方法参数(形参)
2.使用 @Header 注解动态更新请求头,匹配的参数必须提供给 @Header ,若参数值为 null ,这个头会被省略;当传入一个List或array时,为拼接每个非空的item的值到请求头中
3.具有相同名称的请求头不会相互覆盖,而是会照样添加到请求头中
public interface RetrofitService {
@GET("/tasks")
Call<List<Task>> getTasks(@Header("Content-Range") String contentRange);
}
第一种方法
在OKHttpClient interceptors里面进行处理,这样添加的headKey不会覆盖掉 前面的 headKey
okHttpClient.interceptors().add(new Interceptor() {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.addHeader("header-key", "value1")
.addHeader("header-key", "value2");
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
第二种方法
同样在在OKHttpClient interceptors里面进行处理,这样添加的headKey会覆盖掉 前面的 headKey
okHttpClient.interceptors().add(new Interceptor() {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("headerkey", "header-value"); // <-- this is the important line
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
第三种方法:同Headers
利用 retrofit自带的注解,比如我们想要添加这样的请求头:”apikey:81bf9da930c7f9825a3c3383f1d8d766” ,”Content-Type:application/json”;则可以写成如下的 样式
@Headers({"apikey:81bf9da930c7f9825a3c3383f1d8d766" ,"Content-Type:application/json"})
@GET("world/world")
Call<News> getNews(@Query("num") String num,@Query("page")String page);
3:Body
1.作用于方法参数(形参)
2.使用该注解定义的参数不可为null
3.当发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化后的结果直接作为请求体发送出去;如果提交的是一个Map,那么作用相当于 @Field
public interface RetrofitService{
@GET("/users/new")
Call<ResponseBody> createUser(@Body RequestBody requestBody);
}
4: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);
}
5:FieldMap
1.作用于方法参数(形参)
2.表单字段,与Field、FormUrlEncoded 配合;接受 Map 类型,非String类型会调用 toString() 方法
3.map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
public interface RetrofitService{
@FormUrlEncoded
@POST("/things")
Call<List<Repo>> things(@FieldMap Map<String, String> params);
}
6: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)
(1).上传单个文件
接口
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)第二种:上传多个文件(文件个数固定)
@Multipart
@POST("UploadServlet")
Call<ResponseBody> uploadMultipleFiles(
@Part("description") RequestBody description,
@Part MultipartBody.Part file1,
@Part MultipartBody.Part file2);
7:PartMap
1.作用于方法参数(形参)
2.post请求时,提交请求的表单字段,与 @Multipart注解配合
3.map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
4.使用该注解定义的参数类型有以下2种方式可选:
a, 如果类型是RequestBody,那么该值将直接与其内容类型一起使用
b, 其他对象类型将通过使用转换器转换为适当的格式。
5.携带的参数类型更加丰富,包括数据流,所以适用于有文件上传的场景
(2).上传多个文件(文件个数不固定)
接口
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);
}
});
}
8: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);
}
9: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);
}
10: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);
}
11:Url
1.这里是作用于方法参数(形参)
2.直接传入一个请求的 URL变量 用于URL设置
public interface RetrofitService{
@GET
Call<ResponseBody> urlAndQuery(@Url String url, @Query("showAll") boolean showAll);
}
参数注解小结:
请求参数11种比较多,这里小结一下
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等,方便向后台传递数组。
四:注意事项
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()方法中找到不能同时使用的具体代码.
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.”);
总结
@Path:所有在网址中的参数(URL的问号前面),如:http://102.10.93.69/api/Accounts/{accountId}
@Query:URL问号后面的参数,如:http://102.10.93.69/api/Comments?access_token={access_token}
@QueryMap:相当于多个@Query
@Field:用于POST请求,提交单个数据
@Body:相当于多个@Field,以对象的形式提交
其它使用注意防止出错
a:使用@Field时记得添加@FormUrlEncoded
b:上传文件,或者下载文件的时候为避免出错,尽量使用英文命名格式
c:若需要重新定义接口地址,可以使用@Url,将地址以参数的形式传入即可。如
@GET
Call<List<Activity>> getActivityList(
@Url String url,
@QueryMap Map<String, String> map);
Call<List<Activity>> call = service.getActivityList(
"http://115.159.198.162:3001/api/ActivitySubjects", map);
参考与感谢
非常感谢以下文章的作者,
1.【Android】RxJava + Retrofit完成网络请求
2.Retrofit解析及文件上传下载(前后台详细实现)
3.Retrofit使用教程(一)- Retrofit入门详解
4.Retrofit网络请求参数注解
5.Retrofit2.0使用详解
6.解决Retrofit文件下载进度显示问题
文末彩蛋
了解 multipart/form-data
我们这里这是简单的介绍一下:
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);
这里在看看我们构建RequestBody的代码:这里参数至于为什么使用MediaType.parse("multipart/form-data"),需要我们了解http的传输协议:
在最初的http协议中,没有定义上传文件的Method,为了实现这个功能,http协议组改造了post请求,添加了一种post规范,设定这种规范的Content-Type为multipart/form-data;boundary=bound,其中{bound}是定义的分隔符,
用于分割各项内容(文件,key-value对),不然服务器无法正确识别各项内容。post body里需要用到,尽量保证随机唯一。
1.post格式如下:
–${bound}
Content-Disposition: form-data; name=”Filename”
HTTP.pdf
–${bound}
Content-Disposition: form-data; name=”file000”; filename=”HTTP协议详解.pdf”
Content-Type: application/octet-stream
%PDF-1.5
file content
%%EOF
–${bound}
Content-Disposition: form-data; name=”Upload”
Submit Query
–${bound}–
${bound}是Content-Type里boundary的值
- Retrofit2 对multipart/form-data的封装
Retrofit其实是个网络代理框架,负责封装请求,然后把请求分发给http协议具体实现者-httpclient。retrofit默认的httpclient是okhttp。
既然Retrofit不实现http,为啥还用它呢。因为他方便!!
Retrofit会根据注解封装网络请求,待httpclient请求完成后,把原始response内容通过转化器(converter)转化成我们需要的对象(object)。
那么Retrofit和okhttp怎么封装这些multipart/form-data上传数据呢?
在retrofit中:
@retrofit2.http.Multipart: 标记一个请求是multipart/form-data类型,需要和@retrofit2.http.POST一同使用,并且方法参数必须是@retrofit2.http.Part注解。
@retrofit2.http.Part: 代表Multipart里的一项数据,即用${bound}分隔的内容块。
在okhttp3中:
okhttp3.MultipartBody: multipart/form-data的抽象封装,继承okhttp3.RequestBody
okhttp3.MultipartBody.Part: multipart/form-data里的一项数据。
以上内容摘自:一叶扁舟的博客,这是我早期开发一直学习为榜样的大神,大家可以去关注
以上便是《重学RxJava+Retrofit(2):Retrofit网络请求参数详解@Path、@Query、@QueryMap(含文件上传下载)》的全部内容,如你所知,本篇承上启下,方便大家快速查阅Retrofit中的Api,的同时结合Rx会有更好的表现,所以下一篇内容,《几行代码带你实现网络请求》并且分析livery实现原理,敬请期待,
同样上篇《重学RxJava+Retrofit(1):动手写一个Redux的Full Rx架构的注册界面》我完成了一个模拟的登陆注册界面,但是没有实现真正请求网络去登陆,利用本篇或者下一章节内容就能够真正的完成这里的逻辑部分了。
另:Rx操作符不会再写一篇文章,因为网上有很好的现成案例教程,而且时间较少
请尊重劳动成果,注意文中版权声明,Android专栏不定时更新,欢迎点击关注我知乎。也可以同时关注人工智能专栏,本内容作者sunst,有问题请沟通qyddai@gmail.com
作者:sunst 创建日期:2020-05-17 18:02