OkHttp与Retrofit | 我的日常分享

OkHttp与Retrofit

OkHttp与Retrofit

一、OkHttp框架

1.1 什么是OkHttp

https://square.github.io/okhttp/

有Square公司贡献的一个处理网络请求的开源项目,是目前Android使用最广泛的网络框架。从Android 4.4开始HttpURLConnection的底层实现采用的是OkHttp。

  • 支持HTTP/2并允许对同一主机的所有请求共享一个套接字。
  • 如果非HTTP/2,则通过连接池减少了请求延迟。
  • 默认请求GZip压缩数据。
  • 响应缓存,避免了重复请求的网络。
  • … …

android导入依赖(build.gradle):

1
implementation("com.squareup.okhttp3:okhttp:4.9.0")

测试URL:https://www.httpbin.org/

android添加网络权限(AndroidManifest.xml):

1
<uses-permission android:name="android.permission.INTERNET" />

1.2 OkHttp基本用法

OkHttp同步异步请求

1.发送Get请求

同步方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// get方式同步请求
private void getSync() {
// 启动一个线程
new Thread(new Runnable() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient();
// 构建一个链接
Request request = new Request.Builder().url("https://www.httpbin.org/get?a=1&b=2").build();
// 准备好请求的Call对象
Call call = client.newCall(request);
// 发送同步请求
try {
Response response = call.execute();
// 控制台打印
Log.e("MainActivity", response.body().string());

} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}

异步方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// get方式异步请求
private void getAsync() {
/**
* 异步请求并不会阻塞后面代码的执行,
* OkHttp在其内部为我们创建了线程,
* 不用再手动创建线程
*/
OkHttpClient client = new OkHttpClient();
// 构建一个链接
Request request = new Request.Builder().url("https://www.httpbin.org/get?a=1&b=2").build();
// 准备好请求的Call对象
Call call = client.newCall(request);
// 发送异步请求 回调函数
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
/*
连接失败时的回调函数
*/
}

@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
/*
连接成功的回调函数:注意状态码500,404等都算连接成功,
并不是指的请求成功
*/
// 判断状态码是不是200到299
if(response.isSuccessful()){
Log.e("MainActivity", response.body().string());
}
}
});
}

运行结果:

image-20220501175953268

2.发送Post请求

同步方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// post方式同步请求
private void postSync() {
// 创建一个线程
new Thread(new Runnable() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient();
// 创建一个请求体
FormBody formBody = new FormBody.Builder()
.add("username","orange")
.add("password","123456")
.build();
// 构建请求连接
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(formBody)
.build();
// 准备好的Call连接
Call call = client.newCall(request);
// 发送同步请求
try {
Response response = call.execute();
Log.e("MainActivity", response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}

异步方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// post方式异步请求
private void postAsync() {
OkHttpClient client = new OkHttpClient();
// 创建一个请求体
FormBody formBody = new FormBody.Builder()
.add("username","orange")
.add("password","123456")
.build();
// 构建请求连接
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(formBody)
.build();
// 准备好的Call连接
Call call = client.newCall(request);
// 发送异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {

}

@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if(response.isSuccessful()){
Log.e("MainActivity", response.body().string());
}
}
});
}

运行结果:

image-20220501175935138

1.3 POST请求

协议规定POST提交的数据必须放在请求体中,但协议并没有规定数据必须使用什么编码方式。而常用的编码方式有:

菜鸟教程 https://www.runoob.com/http/http-content-type.html

常见的媒体格式类型如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式

以application开头的媒体格式类型:

  • application/x-www-form-urlencoded :

    中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

  • multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

  • application/octet-stream : 二进制流数据(如常见的文件下载)

  • application/json: JSON数据格式

  • … …

案例1:上传文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Test
public void uploadFileTest(){
OkHttpClient okHttpClient = new OkHttpClient();
// 创建一个请求体
File file1 = new File("1.txt");
File file2 = new File("2.txt");

MultipartBody multipartBody = new MultipartBody.Builder()
// 设置接受的key,设置文件名称,加入文件并设置文件类型为文本类型
.addFormDataPart("file1",file1.getName(), RequestBody.create(file1, MediaType.parse("text/plain")))
.addFormDataPart("file2",file2.getName(),RequestBody.create(file1, MediaType.parse("text/plain")))
.build();
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(multipartBody)
.build();
// 得到准备好的Call对象
Call call = okHttpClient.newCall(request);
// 发送请求
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

txt文件内容:

image-20220501184725001

运行结果:

image-20220501184544961

案例2:上传JSON数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void uploadJsonTest(){
OkHttpClient okHttpClient = new OkHttpClient();
// 创建一个请求体
RequestBody requestBody = RequestBody.create("{\"a\":1,\"b\":2}", MediaType.parse("application/json"));
Request request = new Request.Builder()
.url("https://www.httpbin.org/post")
.post(requestBody)
.build();
// 得到准备好的Call对象
Call call = okHttpClient.newCall(request);
// 发送请求
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

运行结果:

image-20220501184443976

1.4 OkHttp 构建者的设置

OkHttp给我们提供了一种构建者的模式来创建OkHttpClient对象。

1
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();

1. 拦截器

1
2
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(匿名内部类).build();
OkHttpClient okHttpClient = new OkHttpClient.Builder().addNetworkInterceptor(匿名内部类).build();

基本使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Test
public void interceptorTest(){
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
// 前置处理
/*
根据传来的chain得到request对应,在原先的request对象
基础上,添加一些信息后,构建新的对象。
*/
Request request = chain.request().newBuilder()
.addHeader("os","android")
.addHeader("version","1.0")
.build();
Response response = chain.proceed(request);
// 后置处理
/*
如果直接返回
return chain.proceed(chain.request());
相当于在拦截器啥都没干
*/
return response;
}
}).build();

// 构建一个链接
Request request = new Request.Builder().url("https://www.httpbin.org/get?a=1&b=2").build();
// 准备好请求的Call对象
Call call = okHttpClient.newCall(request);
// 发送同步请求
try {
Response response = call.execute();
// 控制台打印
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

运行结果:

image-20220501202732924

拦截器可以添加多个:

执行顺序按照添加顺序执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Test
public void muiltInterceptorTest() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("os", "android")
.addHeader("version", "1.0")
.build();
Response response = chain.proceed(request);
return response;
}
})
// 定义第二个拦截器
.addInterceptor(new Interceptor() {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("platform", "xiaomi phone")
.build();
Response response = chain.proceed(request);
return response;
}
})
// 添加第三个拦截器
.addInterceptor(new Interceptor() {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
return chain.proceed(chain.request());
}
})
.build();
Request request = new Request.Builder().url("https://www.httpbin.org/get?a=1&b=2").build();
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

运行结果:

image-20220501203232302

addInterceptor 和 addNetworkInterceptor 的区别:

  1. Application Interceptors
  • 使用 addInterceptor() 注册
  • 有无网络都会被调用到
  • application 拦截器只会被调用一次,调用 chain.proceed() 得到的是重定向之后最终的响应信息,不会通过 chain.connection() 获得中间过程的响应信息
  • 允许 short-circuit (短路) 并且允许不去调用 chain.proceed() 请求服务器数据,可通过缓存来返回数据。
  1. Network Interceptors
  • 使用 addNetworkInterceptor() 注册

  • 无网络时不会被调用

  • 可以显示更多的信息,比如 OkHttp 为了减少数据的传输时间以及传输流量而自动添加的请求头 Accept-Encoding: gzip 希望服务器能返回经过压缩过的响应数据。

  • chain.connection() 返回不为空的 Connection 对象可以查询到客户端所连接的服务器的IP地址以及TLS配置信息。

    拦截器的调用的顺序:

调用顺序是先按 addInterceptor() 设置的顺序遍历,再按 addNetworkInterceptor() 设置的顺序遍历,返回的结果则是反着来,如图:

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Test
public void networkInterceptorTest() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
/*
将addNetworkInterceptor放在addInterceptor之前,
仍然是addInterceptor先执行。
*/
.addNetworkInterceptor(new Interceptor() {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
System.out.println("我是networkInterceptor");
System.out.println("chain.request().header('os') = " + chain.request().header("os"));
return chain.proceed(chain.request());
}
})
.addInterceptor(new Interceptor() {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
System.out.println("我是Interceptor");
Request request = chain.request().newBuilder()
.addHeader("os", "android")
.addHeader("version", "1.0")
.build();
Response response = chain.proceed(request);
return response;
}
})
.build();
Request request = new Request.Builder().url("https://www.httpbin.org/get?a=1&b=2").build();
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

运行结果:

image-20220501204423217

参考文章:OkHttp Interceptor 入门到进阶

2. 缓存与Cookie

OkHttp按照http协议规则实现了缓存的处理。缓存是比如:当我们发起第一次请求之后,如果后续还需要进行同样的请求,此时若果符合缓存规则,则可以减少与服务器的网络通信,直接从本地文件缓存中读取响应返回给请求者。但是默认区情况下,OkHttp的缓存是关闭状态,需要我们开启。

类似浏览器访问页面,浏览器在访问页面时候也会缓存。

image-20220501205011983

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class OkHttpCacheTest {
@Test
public void cacheTest(){
/**
* pathname:缓存文件的地址
* maxSize:缓存最大容量字节 超过这个大小则清楚之前的数据,保留当前的数据
*/
OkHttpClient okHttpClient = new OkHttpClient.Builder()
// 配置缓存
.cache(new Cache(new File("cache.txt"),1024*1024))
.build();
Request request = new Request.Builder().url("http://www.httpbin.org/get").get().build();
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}

Cookie是某些网站为了辨别用户身份,进行会话跟踪(比如确定登录状态),而存储在用于本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。

与浏览器访问网页相似:

image-20220501230050632

使用接口:玩安卓 https://www.wanandroid.com

案例1:登录用户并获取收藏的文章
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class OkHttpCookieTest {
List<Cookie> cookies = new ArrayList<>();
@Test
public void cookieTset(){
OkHttpClient okHttpClient = new OkHttpClient.Builder()
/*
配置Cookie,将Cookie存到内存中
*/
.cookieJar(new CookieJar() {
@Override
public void saveFromResponse(@NonNull HttpUrl httpUrl, @NonNull List<Cookie> list) {
cookies = list;
System.out.println("打印Cookie数据");
System.out.println(cookies.toString());
}

/*
每次发送请求都会调用loadForRequest,
加载Cookie
*/
@NonNull
@Override
public List<Cookie> loadForRequest(@NonNull HttpUrl httpUrl) {
System.out.println("加载了Cookie");
return cookies;
}
})
.build();
System.out.println("用户登录");
/*
登录用户:
用户名:zhangsan321
密码:123456
*/
FormBody formBody = new FormBody.Builder()
.add("username", "zhangsan321")
.add("password", "123456")
.build();
Request request = new Request.Builder().url("https://www.wanandroid.com/user/login")
.post(formBody)
.build();
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}

System.out.println("获取收藏文章列表");
/*
访问收藏列表
*/
request = new Request.Builder()
.url("https://www.wanandroid.com/lg/collect/list/0/json")
.get().build();
call = okHttpClient.newCall(request);
try {
Response response = call.execute();
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}

运行结果:

image-20220501231909767

去掉Cookie的配置:

获取收藏文章列表时,提示请先登录。

image-20220501232429258

OkHttp官网 https://square.github.io/okhttp/

二、Retrofit框架

A type-safe HTTP client for Android and Java.封装了OkHttp,也是有Square公司贡献的一个处理网络请求的开源项目。

https://github.com/square/Retrofit

1.1 Retrofit的基本用法

android导入依赖(build.gradle):

1
com.squareup.retrofit2:retrofit:2.9.0

在retrofit中封装了OkHttp,即导入上面的包,也就是也导入了OkHttp的包implementation("com.squareup.okhttp3:okhttp:4.9.0")。OkHttp的相关类同样可以使用。

开启网络权限:

1
<uses-permission android:name="android.permission.INTERNET" />

根据Http接口创建Java接口。

可以在不同域名使用不用的接口,在这个域名调用这个接口,在另一个域名调用另一个接口或相同的接口。

  1. 创建HTTP接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package cn.yuencode.retrofit_app.http;


    import okhttp3.ResponseBody;
    import retrofit2.Call;
    import retrofit2.http.Field;
    import retrofit2.http.FormUrlEncoded;
    import retrofit2.http.GET;
    import retrofit2.http.POST;
    import retrofit2.http.Query;

    public interface HttpbinService {

    /**
    * 通过@POST注解表示这是post请求,里面的post参数为请求uri;
    * 通过@FormUrlEncoded注解定义以表单方式提交;
    * 函数形参中的 @Field("username") String username;
    * 定义表单提交的字段,将以username = (Sring username)这种形式提交;
    * 后面的形参username可以随意定义,但是@Field注解中的值要与服务器中的接受名称对应。
    *
    * @param username
    * @param password
    * @return
    */
    @POST("post")
    @FormUrlEncoded
    Call<ResponseBody> postRequest(@Field("username") String username, @Field("password") String password);

    /**
    * 通过@GET注解表示以get方式提交,其value表示请求路径;
    * 函数中的@Query注解对应的参数为请求参数
    *
    * @param username
    * @param password
    * @return
    */
    @GET("get")
    Call<ResponseBody> getRequest(@Query("username") String username, @Query("password") String password);
    }

注意:ResponseBodyOkHttp包中的。

  1. 发送请求

    • get同步请求:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // get方式同步请求
      private void getSync() {
      // 创建一个线程
      new Thread(new Runnable() {
      @Override
      public void run() {
      // 1.创建一个Retrofit对象
      Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org/").build();
      // 2.用定义的http接口通过Retrofit创建一个对象
      HttpbinService httpbinService = retrofit.create(HttpbinService.class);
      // 3.调用接口方法返回一个准备好的Call
      //retrofit2.Call<ResponseBody> call = httpbinService.getRequest("zhangsan", "123456");
      Call<ResponseBody> call = httpbinService.getRequest("zhangsan", "123456");
      // 4.发送请求
      try {
      Response<ResponseBody> response = call.execute();
      Log.e("MainAcitity",response.body().string());
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }).start();
      }

运行结果:

image-20220503150744673

  • get异步请求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // get方式异步请求
    private void getAsync() {
    // 1.创建一个Retrofit对象
    Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org/").build();
    // 2.用定义的http接口通过Retrofit创建一个对象
    HttpbinService httpbinService = retrofit.create(HttpbinService.class);
    // 3.调用接口方法返回一个准备好的Call
    //retrofit2.Call<ResponseBody> call = httpbinService.getRequest("zhangsan", "123456");
    Call<ResponseBody> call = httpbinService.getRequest("lisi", "123456");
    // 4.发送请求
    call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
    try {
    Log.e("MainAcitity",response.body().string());
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {

    }
    });
    }

    运行结果:

    可不定义线程,在异步请求中,框架内部为我们创建了线程。

    image-20220503151034122

    • post同步请求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// post方式同步请求
private void postSync() {
// 创建一个线程
new Thread(new Runnable() {
@Override
public void run() {
// 1.创建一个Retrofit对象
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org/").build();
// 2.用定义的http接口通过Retrofit创建一个对象
HttpbinService httpbinService = retrofit.create(HttpbinService.class);
// 3.调用接口方法返回一个准备好的Call
Call<ResponseBody> call = httpbinService.postRequest("zhangsanpost", "123456");
// 4.发送请求
try {
Response<ResponseBody> response = call.execute();
Log.e("MainAcitity",response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}

运行结果:

image-20220503151421057

  • post异步请求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// post方式异步请求
private void postAsync() {
// 1.创建一个Retrofit对象
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org/").build();
// 2.用定义的http接口通过Retrofit创建一个对象
HttpbinService httpbinService = retrofit.create(HttpbinService.class);
// 3.调用接口方法返回一个准备好的Call
//retrofit2.Call<ResponseBody> call = httpbinService.getRequest("zhangsan", "123456");
Call<ResponseBody> call = httpbinService.getRequest("lisipost", "123456");
// 4.发送请求
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.e("MainAcitity",response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {

}
});
}

运行结果:

image-20220503151441853

1.2 Retrofit中的注解

类型 注解
方法注解 @GET、@POST、@PUT、@DELETE、@PATH、@HEAD、@OPTIONS、@HTTP
标记注解 @FormUrlEncoded、@Multipart、@Streaming
参数注解 @Query、@QueryMap、@Body、@Field、@FieldMap、@Part、@PartMap
其他注解 @Path、@Header、@Headers、@Url

1、方法注解

@HTTP:提供指定请求方法与请求路径的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class RetrofitHttpAnnoTest {
@Test
public void HttpAnnoGetTest() {
// 创建retrofit对象
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org").build();
// 创建接口对象
TestService testService = retrofit.create(TestService.class);
// 调用接口方法
Call<ResponseBody> call = testService.getRequest("android");
// 发送请求
try {
Response<ResponseBody> execute = call.execute();
System.out.println("execute.body().string() = " + execute.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

@Test
public void HttpAnnoPostTest() {
// 创建retrofit对象
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org").build();
// 创建接口对象
TestService testService = retrofit.create(TestService.class);
// 调用接口方法
Call<ResponseBody> call = testService.postRequest("zhangsan","123456");
// 发送请求
try {
Response<ResponseBody> execute = call.execute();
System.out.println("execute.body().string() = " + execute.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}

interface TestService {
/*
@HTTP注解:可通过参数方式指定,请求方法与请求路径
*/
@HTTP(method = "GET", path = "get")
Call<ResponseBody> getRequest(@Query("title") String title);

/*
当使用@HTTP请求发送post请求时,如果有请求体,
hasBody = true要设置,默认为false,
需要配合其他对应请求体的注解如 @FormUrlEncoded
*/
@HTTP(method = "POST", path = "post",hasBody = true)
@FormUrlEncoded
Call<ResponseBody> postRequest(@Field("username") String username, @Field("password") String password);
}

2、标记注解

@Multipart、@Streaming 文件下载上传中使用。

3、参数注解

@QueryMap、@FieldMap、@PartMap注解,通过键值对的方式定义参数,对于参数数据多的情况使用此方式,会更加地美观。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class RetrofitParamAnnoTest {
Retrofit retrofit = null;
ParamAnnoService paramAnnoService = null;

@Before
public void before() {
// 创建retrofit对象
retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org").build();
// 创建接口对象
paramAnnoService = retrofit.create(ParamAnnoService.class);
}

@Test
public void queryMapTest() {
Map<String, String> map = new HashMap<>();
map.put("title", "android");
map.put("page", "1");
map.put("size", "10");
// 调用接口方法
Call<ResponseBody> call = paramAnnoService.get(map);
// 发送请求
try {
Response<ResponseBody> execute = call.execute();
System.out.println("execute.body().string() = " + execute.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

@Test
public void fieldMapTest() {
Map<String, String> map = new HashMap<>();
map.put("title", "android");
map.put("page", "1");
map.put("size", "10");
// 调用接口方法
Call<ResponseBody> call = paramAnnoService.post(map);
// 发送请求
try {
Response<ResponseBody> execute = call.execute();
System.out.println("execute.body().string() = " + execute.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}

interface ParamAnnoService {
/*
@QueryMap注解:使用map集合键值对的方式定义请求参数,
map中的键即是服务器中接受到的name,所以@QueryMap中不用传参
*/
@GET("get")
Call<ResponseBody> get(@QueryMap Map<String, String> mapData);

/*
@FieldMap注解:使用map集合键值对的方式定义请求参数,
map中的键即是服务器中接受到的name,所以@FieldMap中不用传参
*/
@POST("post")
@FormUrlEncoded
Call<ResponseBody> post(@FieldMap Map<String, String> mapData);

}

@Body

post(@Body RequestBody body);自定义body参数,OkHttp中的body。自己指定不用使用@FormUrlEncoded注解。

而通过注解@Field等的方式,相当于就是帮我们最后生成了RequestBody

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ParamAnnoService.java
/*
使用@Body注解后,不需要在使用类似@FormUrlEncoded的注解了,
可以根据参数自动识别
*/
@POST("post")
Call<ResponseBody> bodyPostTest(@Body RequestBody body);

RetrofitParamAnnoTest.java
@Test
public void BodyTest(){
// 自定义FormBody
FormBody formBody = new FormBody.Builder()
.add("username","zhangsan")
.add("password","123456")
.build();
Call<ResponseBody> call = paramAnnoService.bodyPostTest(formBody);
// 发送请求
try {
Response<ResponseBody> execute = call.execute();
System.out.println("execute.body().string() = " + execute.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

4、其他注解

@Path:作用于参数,动态指定url请求地址

1
2
3
4
5
/*
通过@Path注解动态指定页码
*/
@GET("/lg/collect/list/{page_size}/json")
Call<ResponseBody> collectList(@Path("page_size") String page);

@Header注解。设置请求头

post(@Header(“os”) String os)

可设置多个。

1
2
@GET("/get")
Call<ResponseBody> getTestHeader(@Header("os") String osName,@Header("version") String version);
1
2
3
4
5
6
7
8
9
10
@Test
public void setHeaderTest(){
Call<ResponseBody> call = otherAnnoService.getTestHeader("android", "3.1.2");
try {
Response<ResponseBody> response = call.execute();
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

运行结果:

image-20220503185803178

@Headers注解:设置多个请求头,定义在方法上面

1
2
3
4
5
6
@GET("get")
@Headers({
"os:android",
"version:4.0.1",
})
Call<ResponseBody> getTestHeaders();
1
2
3
4
5
6
7
8
9
10
@Test
public void setHeadersTest(){
Call<ResponseBody> call = otherAnnoService.getTestHeaders();
try {
Response<ResponseBody> response = call.execute();
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

运行结果:

image-20220503190210065

@Url注解,作用于参数,动态设置请求路径,指定一个本次请求完整的url。

1
2
3
4
5
6
/*
当使用@Url注解时,请求方法类型注解不能带请求路径,
url给定完整的请求路径
*/
@GET
Call<ResponseBody> getTestUrl(@Url String url, @Query("wd") String key);
1
2
3
4
5
6
7
8
9
10
@Test
public void urlTest(){
Call<ResponseBody> call = otherAnnoService.getTestUrl("https://www.httpbin.org/get?page=2","android");
try {
Response<ResponseBody> response = call.execute();
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

image-20220503191400576

注意:当使用@Url注解时,请求方法类型注解不能带请求路径。

1.3 Retrofit的转换器

在我们收到服务器的响应后,目前无论是OkHttp还是Retrofit都只能接受到String字符串类型的数据,在实际开发中,我们经常需要对字符串进行解析将其转变成一个Java Bean对象。

比如,服务器响应的数据为JSON格式字符串,那我们可以自己利用GSON库完成反序列化的操作。而Retrofit提供了多个转换器使得响应能够完成自动的数据转换。

1、 添加依赖:

此依赖内包含了Gson依赖。

1
implementation("com.squareup.retrofit2:converter-gson:2.9.0")

2、定义接口方法:

返回值泛型可不在书写为ResponseBody,可直接改为对应的JavaBean

1
2
3
@POST("post")
@FormUrlEncoded
Call<JavaBean> post(@Field("username") String username, @Field("password") String passwrod);

接口: 玩安卓 https://www.wanandroid.com/ 登录

手动创建JavaBean比较麻烦,可使用工具。

工具:JSON数据转JavaBeanhttps://www.bejson.com/json2javapojo/new/

将返回的JSON粘贴至网页。

image-20220503192147238

image-20220503192420853

3、添加转换器:

1
.addConverterFactory(GsonConverterFactory.create())
1
2
3
4
5
retrofit = new Retrofit.Builder()
.baseUrl("https://www.wanandroid.com/")
// 添加Gson的转换器
.addConverterFactory(GsonConverterFactory.create())
.build();

完整案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package cn.yuencode.retrofit_app;

import org.junit.Before;
import org.junit.Test;

import java.io.IOException;

import cn.yuencode.retrofit_app.entity.BaseBean;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;

public class RetrofitConverterTest {
Retrofit retrofit = null;
ConverterTestService converterTestService = null;
@Before
public void before(){
retrofit = new Retrofit.Builder()
.baseUrl("https://www.wanandroid.com/")
// 添加Gson的转换器
.addConverterFactory(GsonConverterFactory.create())
.build();
converterTestService = retrofit.create(ConverterTestService.class);
}

@Test
public void testLogin(){
Call<BaseBean> call = converterTestService.login("zhangsan321", "123456");
Response<BaseBean> baseBeanResponse = null;
try {
baseBeanResponse = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("baseBeanResponse.body() = " + baseBeanResponse.body());
}

}

interface ConverterTestService{
@POST("/user/login")
@FormUrlEncoded
Call<BaseBean> login(@Field("username") String username, @Field("password") String password);
}

运行结果:

image-20220503193508810

1.4 Retrofit适配器与嵌套请求

先登录后才能请求收藏文章列表。

Retrofit的接口方法返回值必须是Call,如果能够将Call改为RxJava中的Observable,对于嵌套的情况,就能够得到非常方便优雅的解决。这就是适配器的功能,如果我们想要返回的不是Call,适配器就能够帮助我们转换为其他类型。

不使用嵌套请求,就需要嵌套回调,一个回调里面又有一个回调,非常麻烦不美观。

添加依赖:

1
2
3
implementation("com.squareup.retrofit2:adapter-rxjava:3.2.9.0")
// 如果是android开发,一般会引入下面这个rxandroid依赖,提供了安卓特有的一些配置,比如主线程
implementation("io.reactivex.rxjava3:rxandroid:3.0.0")

添加适配器:

1
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())

自定义OkHttp,添加Cookie配置

1
2
3
4
5
6
7
8
9
10
11
.callFactory(new OkHttpClient.Builder().cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {

}

@Override
public List<Cookie> loadForRequest(HttpUrl url) {
return null;
}
}).build())

1.5 文件的上传下载

上传文件:

1.txt

1
1234567890
1
2
3
@POST("/post")
@Multipart
Call<ResponseBody> uploadFile(@Part MultipartBody.Part part);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testUploadFile(){
// File file = new File("avatar.png");
File file = new File("1.txt");
MultipartBody.Part part = MultipartBody.Part
.createFormData("file","1.txt", RequestBody.create(MediaType.parse("text/html"), file));

Call<ResponseBody> call = fileTestService.uploadFile(part);
try {
Response<ResponseBody> response = call.execute();
System.out.println("response.body().string() = " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

运行结果:

image-20220503195906562

下载文件:

文件比较大可以使用@Sreaming注解,避免内存溢出。

1
2
3
@GET
@Streaming
Call<ResponseBody> downloadFile(@Url String url);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 下载文件
@Test
public void testDownloadFile() throws IOException {
Call<ResponseBody> call = fileTestService.downloadFile("http://pan.yuencode.cn/externalLinksController/chain/ToDesk_Setup.exe?ckey=Ik77ZxRTGL%2BNNHo1AnerUfIG0o3mGa6Is5nGfMOFX1ebBqLQKsXnJ2Gjg8jOk8H2");
Response<ResponseBody> response = call.execute();
File file = new File("1.exe");
FileOutputStream fos = new FileOutputStream(file);
InputStream inputStream = response.body().byteStream();
byte[] buffer = new byte[1024*1024];
int len;
while ((len = inputStream.read(buffer)) != -1){
fos.write(buffer,0,len);
}
}

运行结果:

image-20220503220955183

本篇文章中完整案例代码 https://git.yuencode.cn/jiaxiaoyu/HttpDemo.git