Retrofit 是 Square 公司开源的一个高质量高效率的http库,开发者是被称为 Android 之神 的 Jake Wharton。
Retrofit 以其解耦彻底、扩展灵活、使用简单等特性,在 Android 领域声名远播。
Retrofit 已经出来很久了,现在最新版本是 2.3.0
,如果还没使用过它,就真的是 low 爆了。
这里简单讲解 Retrofit 的使用以及理清楚 Retrofit 的上传。
Retrofit 入门
Retrofit 其实相当简单,简单到源码只有37个文件,其中22个文件是注解还都和HTTP有关,真正暴露给用户的类并不多。Retrofit 必须配合 Okhttp 来使用,并且真正的网络请求是由 Okhttp 完成的,Retrofit 是利用动态代理对数据做了封装后,给到 OKhttp ,最后由Okhttp 完成网络请求后,将数据交给 Retrofit 。
1.创建Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://gank.io/api/")
.build();
创建Retrofit实例时需要通过Retrofit.Builder
,并调用baseUrl
方法设置URL,当然,baseUrl 并不是必须的,也可以在后面的接口上设置。
注: Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出
IllegalArgumentException: baseUrl must end in /
的异常
2.定义 API 接口
这里以 「干货集中营」的 API 为例,定义了一个 GET 请求的接口。
public interface API {
/**
* 妹纸列表
*/
@GET("data/福利/{num}/{page}") //里面填写全路径URl,如果设置有baseURL,也可以是相对路径
Call<ResponseBody> getGirlPic(@Path("num") int num, @Path("page") int page);
@GET() //也可以不填,然后在参数里面指定全路径的URL
Call<ResponseBody> getGirlPic(@Url String url);
}
这个interface
内的方法我们是无法直接调用的,我们需要通过Retrofit 创建一个API
的代理对象。
API apiServer = retrofit.create(API.class);
然后,通过这个代理对象来调用方法。
3.调用接口
API apiServer = retrofit.create(API.class);
Call<ResponseBody> girlPic = apiServer.getGirlPic(8, 1);
//调用后,回调方法执行在主线程
girlPic.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
这样就完成了retrofit 简单的调用了。
Retrofit 注解
想要愉快的使用 Retrofit ,就必须要了解它的22个注解的用途。
为更好理解,可以将这22个注解分为三类。
HTTP 请求类型注解
上面 8 个注解都是在方法上使用,除了「 HTTP」 这个注解,其它都对应了HTTP 协议的 7 种请求类型,这个很好理解,只有 HTTP 这个注解有点特殊。
HTTP 注解可以代替以上方法中的任意一个注解,有 3 个属性:method
、path
、hasBody
。
下面是用HTTP注解实现:
public interface API {
/**
* method 表示请求的方法,区分大小写
* path表示路径
* hasBody表示是否有请求体
*/
@HTTP(method = "GET", path = "data/福利/{num}/{page}", hasBody = false)
Call<ResponseBody> getGirlPic(@Path("num") int num, @Path("page") int page);
}
标注类注解
这 3 个注解也是作用在方法上的,前两个表单请求的注解「FormUrlEncoded」、「Multipart」用于上传,后面会详细了解。
最后一个注解「Streaming」用于文件的下载:
@GET
@Streaming
Call<ResponseBody> download(@Url String url);
「Streaming」注解表示以流的形式返回响应体,避免了因数据过大而造成的内存溢出。
参数类注解
上面的注解都有各自的作用,多多练习才能知道每个注解的具体用法。
GET 请求非常简单,网上有很多示例,例如:
@GET("group/users")
Call<ResponseBody> groupList(@Query("sort") String sort);
//Query注解用于添加参数,可添加多个
//如果参数个数不固定,可以使用QueryMap注解用于动态添加参数
@GET("group/users")
Call<ResponseBody> groupList(@QueryMap Map<String, String> options);
复杂的是 POST 请求,许多文章都讲得不够详细完整,这里整理记录一下。
关于 POST 请求上传,可以简单分为 参数上传、文件上传、混合上传 。
参数上传
参数上传非常常见,在用户登录时,一般都会用到参数上传,将用户名密码上传到服务器 做校验。
这种表单提交需要用到 「FormUrlEncoded」注解。
参数上传可以分为 固定参数上传、动态参数上传、自定义上传。
固定参数上传
固定参数是指:接口上传的参数个数固定不变。
@FormUrlEncoded
@POST("/upload")
Call<ResponseBody> uploadParams(
@Field("username")String username,
@Field("password")String password);
动态参数上传
动态参数上传是指:接口上传的参数动态改变。
这种情况可以使用 「FieldMap」来添加参数。
@FormUrlEncoded
@POST("/upload")
Call<ResponseBody> uploadParams(@FieldMap Map<String,String> map);
自定义上传
自定义上传是指:直接封装请求体。
@POST("/upload")
Call<ResponseBody> uploadParams(@Body RequestBody body);
//组装请求体
FormBody body=new FormBody.Builder()
.add("username","admin")
.add("token","psd=dmzkkshf")
.build();
文件上传
文件上传也有许多应用场景,比如图片上传。
单文件上传
@Multipart
@POST("upload")
Call<ResponseBody> uploadOneFile(@Part MultipartBody.Part body);
//第一个参数表示文件的 MIME类型,第二个参数是文件
RequestBody fileRQ = RequestBody.create(MediaType.parse("image/*"), file);
//第一个参数表示接口定义的参数名称,第二个参数表示文件的名称,第三个参数是 RequestBody
MultipartBody.Part part = MultipartBody.Part.createFormData("picture", file.getName(), fileRQ);
Call<ResponseBody> uploadCall = downloadService.uploadOneFile(part);
MediaType 指的是要传递的数据的MIME类型。
MediaType对象包含了三种信息:type 、subtype以及charset,一般将这些信息传入parse()方法中,这样就可以解析出MediaType对象,
比如 “text/x-markdown; charset=utf-8” ,type值是text,表示是文本这一大类;/后面的x-markdown是subtype,表示是文本这一大类下的markdown这一小类; charset=utf-8 则表示采用UTF-8编码。
如果不知道某种类型数据的MIME类型,可以参见链接Media Type 和 MIME 参考手册,较详细的列出了所有数据的MIME类型。
常见的 MIME 类型如下:
- json : application/json
- xml : application/xml
- png : image/png
- jpg : image/jpeg
- gif : imge/gif
多文件上传
@PartMap
@Multipart
@POST("upload")
Call<ResponseBody> uploadFiles(@PartMap Map<String, RequestBody> map);
RequestBody fb = RequestBody.create(MediaType.parse("text/plain"), "hello,retrofit");
RequestBody fileTwo = RequestBody.create(MediaType.parse("image/*"), new File(Environment.getExternalStorageDirectory()
+ file.separator + "original.png"));
Map<String, RequestBody> map = new HashMap<>();
//这里的key必须这么写,否则服务端无法识别
map.put("file\"; filename=\""+ file.getName(), fileRQ);
map.put("file\"; filename=\""+ "2.png", fileTwo);
Call<ResponseBody> uploadCall = downloadService.uploadFiles(map);
@Part
@Multipart
@POST("upload")
Call<ResponseBody> uploadFiles(@Part List<MultipartBody.Part> parts);
RequestBody fileRQ = RequestBody.create(MediaType.parse("image/*"), file);
MultipartBody.Part part = MultipartBody.Part.createFormData("picture", file.getName(), fileRQ);
RequestBody fb = RequestBody.create(MediaType.parse("text/plain"), "hello,retrofit");
RequestBody fileTwo = RequestBody.create(MediaType.parse("image/*"), new File(Environment.getExternalStorageDirectory()
+ file.separator + "original.png"));
MultipartBody.Part two=MultipartBody.Part.createFormData("one","one.png",fileTwo);
List<MultipartBody.Part> parts=new ArrayList<>();
parts.add(part);
parts.add(two);
Call<ResponseBody> uploadCall = downloadService.uploadFiles(parts);
文件和参数混合上传
@Multipart
@POST("upload")
Call<ResponseBody> uploadFile(
@Part("body") RequestBody body,
@Part MultipartBody.Part file);
MultipartBody.Part part = MultipartBody.Part.createFormData("picture",
file.getName(),
fileRQ);
RequestBody fb =RequestBody.create(MediaType.parse("text/plain"), "hello,retrofit");
Call<ResponseBody> uploadCall = downloadService.uploadFile(fb,part);
通用上传方式
@POST("upload")
Call<ResponseBody> uploadFile(@Body RequestBody body);
String path = Environment.getExternalStorageDirectory() + File.separator + "1.png";
File file = new File(path);
RequestBody fileRQ = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part part = MultipartBody.Part.createFormData("picture",
file.getName(),
fileRQ);
RequestBody body=new MultipartBody.Builder()
.addFormDataPart("userName","lange")
.addFormDataPart("token","dxjdkdjkj9203kdckje0")
.addFormDataPart("header",file.getName(),fileRQ)
.build();
Call<ResponseBody> uploadCall = downloadService.uploadFile(body);
注意:
@Part 在标注 RequestBody 时,必须携带参数
在标注MultipartBody.Part 时,绝对不能携带参数,如果弄错了,就会直接报异常。
//错误示例代码
@Multipart
@POST("upload")
Call<ResponseBody> uploadOneFile(@Part RequestBody file);//报错
//@Part 标注 RequestBody 时,需要携带参数 如: @Part("file")
//错误示例代码
@Multipart
@POST("upload")
Call<ResponseBody> uploadOneFile(@Part("file") MultipartBody.Part file);//报错
//@Part 标注 MultipartBody.Part 时,不能携带参数