$.post请求的参数在后台代码中得到为null_重学RxJava+Retrofit(2):Retrofit网络请求参数详解Path(含文件上传下载)...

7fc47062a4d86bfbe96303d6b9df34f7.png

 背景图来源:livery情景专属小红图片

解决Okhttp的Response#body()#string()后Response返回体为空问题,在使用livery可以很容易的避免返回空null值无法解析的问题,这一点后面《几行代码带你实现网络请求》中会统一处理

这次废话少说,直接开始Retrofit注解参数介绍,

Retrofit网络请求参数详解@Path、@Query、@QueryMap(含文件上传下载)

Retrofit通过注解的方式,进行网络请求。根据功能分类,注解可以分为:

2a3bdc9ed9ac806bbbd1b64aa84071f6.png
图1:Retrofit注解类型

为了方便大家操作测试和下篇《拥抱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的值

  1. 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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值