初识Retrofit2

基本使用

基本的使用方法官网就有一个点这里,步骤大致可能分为以下几步。

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 result = response.body();
    log(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 类型,非 RequestBody 会通过 Converter 转换
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中的参数使用QueryQueryMap 代替,保证接口定义的简洁 *注2:*QueryFieldPart这三者都支持数组和实现了Iterable接口的类型,如ListSet等,方便向后台传递数组。

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 使用教程

你可能感兴趣的:(初识Retrofit2)