基本使用
基本的使用方法官网就有一个点这里,步骤大致可能分为以下几步。
1.创建接口,使用注解声明url和参数
public interface GitHubService {
@GET("users/{user}/repos")
Call> listRepos(@Path("user") String user);
}
2.创建Retrofit实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
3.创建接口实例
GitHubService service = retrofit.create(GitHubService.class);
4.接口调用
Call> repos = service.listRepos("octocat");
5.发起请求
//同步请求
call.execute();
//异步请求
call.enqueue(new Callback(){
@Override
public void onResponse (Call << T > call, Response < Result < User >> response){
}
@Override
public void onFailure (Call << T > call, Throwable throwable){
}
});
json解析
retrofit支持添加json解析器,将返回的json自动转换成模型。支持的解析器
Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
JAXB: com.squareup.retrofit2:converter-jaxb
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
添加Gson解析器
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
baseUrl
Retrofit2 的baseUrl 真的必须以 /(斜线) 结尾吗?
常见注解
@GET
用于get请求,写在方法外,一般后面会跟上请求的路由
@GET("getAll")
Call> getAll();
@Query
设置查询参数,直接拼接在url后面
@GET("getById")
Call> getById(@Query("id") long id);
请求的url:baseUrl/getById?id=真实参数
@QueryMap
和Query的功能一样,用于查询参数很多的时候使用map代替。
@GET("getUser")
Call> getUser(@QueryMap Map params);
请求的url:baseUrl/getById?key1=value1&key2=value2
@Path
路径参数,动态替换url中的参数
@GET("get/{id}")
Call> getWithPath(@Path("id") long id)
请求的url:baseUrl/get/真实参数
@Url
当请求的路由不确定,需要通过参数传递的时候使用
@GET
Call> getDynamicUrl(@Url String url);
@get
后面不跟路由,使用参数中的url。
@Headers
静态添加头部信息:包含添加单个头部、添加多个头部
通过@Headers("")注解,内部以key:value的方式填写内容
@GET("getWithHead")
@Headers("name:test")
Call> getWithHead1();
@Header
动态添加单个请求头数据
@GET("getWithHead")
Call> getWithHead2(@Header("name") String name);
@HeaderMap
动态添加多个头部信息,和@Header的功能一样。用map来代替多个参数。
@POST
用于post请求,写在方法外
@FormUrlEncoded
表单请求,和@Field
配合使用。会将请求参数转为application/x-www-form-urlencoded
@POST("post")
@FormUrlEncoded
Call> post(@Field("id") long id, @Field("name") String name);
@Field
表单请求的字段,见上例
@FieldMap
多个表单请求的参数
@Body
对应服务端的content-type 是application/json。
对应springBoot中的@RequestBody
@POST("postBody")
Call> postBody(@Body User user);
注意:使用@Body注解的时候要使用json的解析转换。
@Multipart
用于上传文件
包含文件上传的请求不是普通的表单请求。
Content-Type:multipart/form-data
不能使用@FormUrlEncoded,要修改为@Multipar
参数也不能使用@Field,要使用@Part
上传的文件类型要声明称MultipartBody.Part
类型。
传参的时候也要麻烦一些。
public void postFile()throws IOException{
File file = new File("/Users/yeqiu/Desktop/测试文档/pdf/悟空线上合同模板V20190815.pdf");
RequestBody fileBody = RequestBody.create(MediaType.parse("file/pdf"), file);
MultipartBody.Part file1 = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
Call> call = getApi().postFile("悟空线上合同模板V20190815", file1);
Response> response = call.execute();
Result result = response.body();
log(result);
}
关于一些请求的传参
上传list参数
如果list泛型是基本数据类型可以直接使用list类型的参数。
@POST("post/list")
@FormUrlEncoded
Call> postList(@Field("title") String title,
@Field("list") List list);
如果泛型是引用数据类型就,接口方法的参数就要设置成map。
@POST("post/multipart/data")
@Multipart
Call> postMultipartData(@PartMap Map params,
@Part List files);
调用的时候需要使用[]
和索引来拼接list数据
@Test
public void postMultipartData() throws IOException {
User user = new User();
user.setId(1);
user.setName("宫本武藏");
User user2 = new User();
user2.setId(2);
user2.setName("蓝色超级兵");
List users = new ArrayList<>();
users.add(user);
users.add(user2);
RequestBody nameBody = RequestBody.create(MediaType.parse("text/plain"), "名字");
RequestBody titleBody = RequestBody.create(MediaType.parse("text/plain"), "标题");
Map params = new HashMap<>();
params.put("name", nameBody);
params.put("title", titleBody);
for (int i = 0; i < users.size(); i++) {
User u1 = users.get(i);
RequestBody id = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(u1.getId()));
RequestBody name = RequestBody.create(MediaType.parse("text/plain"), u1.getName());
params.put("users[" + i + "].id", id);
params.put("users[" + i + "].name", name);
}
List files = new ArrayList<>();
files.add(new File("/Users/yeqiu/Desktop/测试文档/pdf/悟空线上合同模板V20190815.pdf"));
files.add(new File("/Users/yeqiu/Desktop/测试文档/pdf/表格测试.pdf"));
files.add(new File("/Users/yeqiu/Desktop/测试文档/pdf/印章位置.pdf"));
List parts = new ArrayList<>();
for (int i = 0; i < files.size(); i++) {
File file = files.get(i);
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("files[" + i + "]", file.getName(), fileBody);
parts.add(filePart);
}
Call> call = getApi().postMultipartData(params,parts);
Response> response = call.execute();
Result
这个接口接受的数据模型为
private String name;
private String title;
private List users;
private List files;
参数为复杂对象嵌套时
接口声明的类型为
public class ComplexData {
public User user;
private String str;
}
public class User {
private long id;
private String name;
}
调用接口穿参时必须要加上引用对象的名字
@POST("post/complexData")
@FormUrlEncoded
Call> complexData(@Field("user.id") long id,
@Field("user.name")String name,
@Field("str")String str);
注解详情
请求方法
名称 | 备注 |
---|---|
GET | get请求 |
POST | post请求 |
PUT | put请求 |
DELETE | delete请求 |
PATCH | patch请求 |
HEAD | head请求 |
OPTIONS | options请求 |
HTTP | 可以替换以上7个请求 |
@HTTP的示例
@HTTP(method = "GET", path = "blog/{id}", hasBody = false)
Call getBlog(@Path("id") int id);
表单请求
名称 | 备注 |
---|---|
FormUrlEncoded | 表示请求体是From表单 Content-Type:application/x-www-form-urlencoded |
Multipart | 表示请求体是一个支持文件上传的From表单 Content-Type:multipart/from-data |
名称 | 备注 |
---|---|
Streaming | 表示响应体的数据用流的形式返回 如果没有使用这个注解,默认会将数据载入内存,之后读取数据也是从内存中读取。如果数据量很大就要使用这个注解 |
参数类
名称 | 备注 |
---|---|
Headers | 添加多个请求头,用于方法外的注解 |
Header | 动态添加请求头,只能用于方法参数中 |
Body | 非表单请求体 |
Field | form表单中每个字段名字以及相应数值 @Field的用法类似于@Query,不同的是@Field主要用于Post请求 |
FieldMap | 表单域集合 用于Post请求数据,@FieldMap的用法类似于@QueryMap |
Part | Post提交分块请求 (表单字段,与 PartMap 配合,适合文件上传情况) |
PartMap | @PartMap 表单字段,与 Part 配合,适合文件上传情况;默认接受 Map |
Path | 路径参数,用于替换url路径中的变量字符替换,也就是url中的{}中的部分 |
Query | 单个查询参数,将接口url中追加类似于"?page=1"的字符串,形成提交给服务器端的参数, 主要用于Get请求数据,用于拼接在拼接在url路径后面的查询参数,一个@Query相当于拼接一个参数 |
QueryMap | 查询参数集合,将url中追加类似于"?page=1&count=20"的字符串,形成提交给服务器端的参数. 效果等同于多个@Query参数拼接,主要用于Get请求网络数据 |
Url | 使用此注解参数后,@GET后无需在添加任何内容. 方法中的@Url参数可以是全路径参数,也可以是子路径参数,但是baseurl必须要指定. |
{占位符}和PATH
尽量只用在URL的path部分,url中的参数使用Query
和QueryMap
代替,保证接口定义的简洁 *注2:*Query
、Field
和Part
这三者都支持数组和实现了Iterable
接口的类型,如List
,Set
等,方便向后台传递数组。
Call getByIds(@Query("ids[]") List ids);
//结果:ids[0]=0&ids[1]=1&ids[]=2
拦截器
Retrofit依然使用okHttp的拦截器。
实现okhttp3.Interceptor接口创建拦截器。
例:
public class RetrofitInterceptor implements Interceptor {
private String baseUrl;
private String token;
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
Request request = builder.build();
Response response = null;
if (request.url().toString().startsWith(xarUrl)) {
// 添加请求头
Headers headers = request.headers();
if (headers.get("authorization") == null) {
//添加head后,发起请求
builder.addHeader("authorization", "bearer " + token);
Request headRequest = builder.build();
response = chain.proceed(headRequest);
}
}
if (null == response) {
response = chain.proceed(request);
}
//校验是否登录失效
boolean tokenExpired = isTokenExpired(response);
if (tokenExpired) {
//登录
String newToken = getNewToken();
Request newRequest = chain.request()
.newBuilder()
.addHeader("authorization", "bearer " + newToken)
.build();
response.close();
response = chain.proceed(newRequest);
}
//打印请求体
RetrofitLog.logRequest(request);
//打印响应体
RetrofitLog.logResponse(response);
return response;
}
private boolean isTokenExpired(Response response) {
return response.code() == 401;
}
private String getNewToken() {
return xarToken;
}
}
public class RetrofitLog {
public static void logRequest(Request request) throws IOException {
RequestBody requestBody = request.body();
StringBuilder log = new StringBuilder();
log.append("----> 请求开始 ")
.append(" url = ")
.append(request.url())
.append(" ")
.append(",method = ")
.append(request.method())
.append(" ");
if (requestBody != null) {
log.append(",content-type = ")
.append(requestBody.contentType())
.append(" ");
}
Headers headers = request.headers();
if (headers.size() > 0) {
log.append(",head = ");
JsonObject headJson = new JsonObject();
for (int i = 0; i < headers.size(); i++) {
headJson.addProperty(headers.name(i), headers.value(i));
}
log.append(headJson.toString());
}
JsonObject requestBodyString = new JsonObject();
if (requestBody instanceof FormBody) {
FormBody formBody = (FormBody) requestBody;
for (int i = 0; i < formBody.size(); i++) {
requestBodyString.addProperty(formBody.encodedName(i), URLDecoder.decode(formBody.encodedValue(i)
, "UTF-8"));
}
}
if (requestBody instanceof MultipartBody) {
MultipartBody multipartBody = (MultipartBody) requestBody;
Buffer buffer1 = new Buffer();
requestBody.writeTo(buffer1);
String postParams = buffer1.readUtf8();
String[] split = postParams.split("\n");
List names = new ArrayList<>();
for (String s : split) {
if (s.contains("Content-Disposition")) {
names.add(s.replace("Content-Disposition: form-data; name=", "").replace("\"", ""));
}
}
List parts = multipartBody.parts();
for (int i = 0; i < parts.size(); i++) {
MultipartBody.Part part = parts.get(i);
RequestBody body1 = part.body();
if (body1.contentLength() < 100) {
Buffer buffer = new Buffer();
body1.writeTo(buffer);
String value = buffer.readUtf8();
//打印 name和value
if (names.size() > i) {
requestBodyString.addProperty(names.get(i), value);
}
} else {
if (names.size() > i) {
requestBodyString.addProperty(names.get(i), "");
}
}
}
}
if (requestBody != null) {
String body = "";
if (requestBodyString.size() == 0) {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
try {
body = URLDecoder.decode(buffer.readUtf8(), "UTF-8");
} catch (Exception e) {
body = buffer.readUtf8();
}
} else {
body = requestBodyString.toString();
}
log.append(",requestBody = ")
.append(body)
.append(" ");
}
System.out.println(log.toString());
}
public static void logResponse(Response response) throws IOException {
ResponseBody responseBody = response.peekBody(1024 * 1024);
StringBuilder log = new StringBuilder();
try {
log.append("<---- 请求结束 ")
.append(",url = ")
.append(response.request().url())
.append(" ")
.append(",status = ")
.append(response.code())
.append(" ")
.append(",responseBody = ")
.append(responseBody.string());
} catch (Exception e) {
}
System.out.println(log.toString());
}
}
其他
1.当@GET或@POST注解的url为全路径时(可能和baseUrl不是一个域),会直接使用注解的url的 域。
2.以上介绍的注解可以混合使用
本文中的代码示例已经上传到github
上,传送门,使用IDEA打开,运行RetrofitApplication
后可使用RetrofitTestTest
单元测试。
你真的会用Retrofit2吗?Retrofit2完全教程
Retrofit2 的baseUrl 真的必须以 /(斜线) 结尾吗?
Retrofit2 完全解析 探索与okhttp之间的关系
这是一份很详细的 Retrofit 2.0 使用教程