基础

请求方法

OkHttps 使用sync(String url)async(String url)方法发起 同步和异步的 HTTP 请求,它们支持的方法有:

HTTP 请求方法实现方法Restful 释义起始版本
GETget()获取资源1.0.0
HEADhead()获取资源头信息2.0.0
POSTpost()提交资源1.0.0
PUTput()更新资源1.0.0
PATCHpatch()部分更新资源2.0.0.RC
DELETEdelete()删除资源1.0.0
其它request(String method)任何 HTTP 请求方法2.0.0.RC

例如,修改 ID 为 100 的书的标题,可以这样请求:

http.async("/books/100")
        .addBodyPara("title", "新标题")
        .patch();                   // 发起 PATCH 请求
1
2
3

再如,删除 ID 为 100 的书,可以发起这样的请求:

http.async("/books/100").delete();  // 发起 DELETE 请求
1

再如,使用 HEAD 请求获取服务器上文件的大小:

HttpResult res = http.sync("/download/file.zip").head();
// // HttpResult#getContentLength() 新增于 2.0.0 版本
System.out.println("size = " + res.getContentLength());
1
2
3

你还可以自定义一些请求方法,例如:

http.async("/somethings")
        .setOnResponse((HttpResult result) -> {
            System.out.println(result);
        })
        .request("OPTIONS");        // 发起 OPTIONS 请求
1
2
3
4
5

另外,同步请求的所有这些方法都会返回一个HttpResult,而异步请求则会返回一个HttpCall

HttpResult res1 = http.sync("/books").get();     // 同步 GET
HttpResult res2 = http.sync("/books").post();    // 同步 POST

HttpCall call1 = http.async("/books").get();     // 异步 GET
HttpCall call2 = http.async("/books").post();    // 异步 POST
1
2
3
4
5

回调函数

OkHttps 的回调函数全部使用单方法模式,这样可以充分利用 Java8 或 Kotlin 中的 Lambda 表达式,使你的代码更加简洁优雅:

普通回调

http.async("/users")        // http://api.demo.com/users
        .setOnResponse((HttpResult res) -> {
            // 响应回调
            int status = res.getStatus();       // 状态码
            Headers headers = res.getHeaders(); // 响应头
            Body body = res.getBody();          // 报文体
        })
        .setOnException((IOException e) -> {
            // 异常回调
        })
        .setOnComplete((State state) -> {
            // 完成回调,无论成功失败都会执行,并且在 响应|异常回调 之前执行
            // 可以根据 state 枚举判断执行状态:
            // State.CANCELED`      请求被取消
            // State.RESPONSED`     已收到响应
            // State.TIMEOUT`       请求超时
            // State.NETWORK_ERROR` 网络错误
            // State.EXCEPTION`     其它请求异常
        })
        .get();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

OkHttps 同时还支持 全局回调回调阻断 机制,详见 全局回调监听

TIP

  • 只有异步请求才可以设置这三种(响应|异常|完成)回调
  • 同步请求直接返回结果,无需使用回调

便捷回调

OkHttps 自 v2.1.0 起,对异步请求提供了 6 种便捷回调方法,它们都是OnResponse回调的简化版,当在具体请求中不关心状态码和响应头信息时,使用起来非常方便:

http.async("/users")        // http://api.demo.com/users
        .setOnResBody(Body body -> {
            // 得到响应报文体 Body 对象
        })
        .setOnResMapper(Mapper mapper -> {
            // 得到响应报文体反序列化后的 Mapper 对象
        })
        .setOnResArray(Array array -> {
            // 得到响应报文体反序列化后的 Array 对象
        })
        .setOnResBean(User.class, User bean -> {
            // 得到响应报文体根据 Bean.class 反序列化后的 Java Bean 对象
        })
        .setOnResBean(new TypeRef<Result<User>>(){}, Result<User> result -> {
            // 得到响应报文体根据 TypeRef 反序列化后的 复合泛型 对象
        })
        .setOnResList(User.class, List<User> list -> {
            // 得到响应报文体根据 Bean.class 反序列化后的 Java Bean 列表
        })
        .setOnResString(String str -> {
            // 得到响应报文体的字符串 String 对象
        })
        .get();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

以上的便捷回调里,无法像OnResponse那样获得响应的状态码和头信息,所以最好和 全局回调监听 配合使用。在全局回调里对错误码先做统一处理。

进度回调

上传进度回调:

http.sync("/upload")
        .addFilePara("avatar", "D:/image/avatar.jpg")
        .setOnProcess((Process process) -> { 
            long doneBytes = process.getDoneBytes();   // 已上传字节数
            long totalBytes = process.getTotalBytes(); // 总共的字节数
            double rate = process.getRate();           // 已上传的比例
            boolean isDone = process.isDone();         // 是否上传完成
        })
        .post();
1
2
3
4
5
6
7
8
9

关于上传的更多细节请参考 文件上传 章节。

下载进度回调:

http.sync("/download/test.zip").get().getBody()
        .setOnProcess((Process process) -> {
            long doneBytes = process.getDoneBytes();   // 已下载字节数
            long totalBytes = process.getTotalBytes(); // 总共的字节数
            double rate = process.getRate();           // 已下载的比例
            boolean isDone = process.isDone();         // 是否下载完成
        })
        .toFolder("D:/download/")        // 指定下载的目录,文件名将根据下载信息自动生成
        .start();
1
2
3
4
5
6
7
8
9

还有下载成功或失败的回调:

http.sync("/download/test.zip").get().getBody()
        .toFile("D:/download/test.zip")              // 下载到文件
        .setOnSuccess((File file) -> {
            // 下载成功
        })
        .setOnFailure((Failure failure) -> {
            // 下载失败
        })
        .start();
1
2
3
4
5
6
7
8
9

关于下载的更多内容,请参考 文件下载 章节。

WebSocket 回调

http.webSocket("/websocket-endpoint")
        .setOnOpen((WebSocket ws, HttpResult res) -> {
            // WebSocket 连接建立回调
        })
        .setOnMessage((WebSocket ws, Message msg) -> {
            // 服务器下发消息回调
        })
        .setOnException((WebSocket ws, Throwable thr) -> {
            // 连接发生异常回调
        })
        .setOnClosing((WebSocket ws, WebSocket.Close close) -> {
            // 连接正在关闭回调
        })
        .setOnClosed((WebSocket ws, WebSocket.Close close) -> {
            // 连接已关闭(v2.0.0 之后包含连接被取消 和 连接发生异常)
            boolean isCanceled = close.isCanceled();    // from v2.0.0
            boolean isException = close.isException();  // from v2.0.0
            int code = close.getCode();
            String reason = close.getReason();
        })
        .listen();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

TIP

OkHttps 的所有回调函数都默认在 IO 线程执行,若要切换默认线程(例如 Android 中切换到 UI 线程)请参见 回调线程切换

HttpResult

HttpResult是HTTP请求执行完后的结果,它是同步请求方法get()post()等的返回值,也是异步请求响应回调OnResponse的参数,它定义了如下方法:

  • getState() 得到请求执行状态枚举,它有以下取值:
    • State.CANCELED 请求被取消
    • State.RESPONSED 已收到响应
    • State.TIMEOUT 请求超时
    • State.NETWORK_ERROR 网络错误
    • State.EXCEPTION 其它请求异常
  • getTask() 获取所属的请求任务(v2.5.0 新增)
  • getStatus() 得到 HTTP 状态码
  • isSuccessful() 是否响应成功,状态码在 [200..300) 之间
  • getHeaders() 得到HTTP响应头
  • getHeaders(String name) 得到HTTP响应头
  • getHeader(String name) 得到HTTP响应头
  • getContentLength(); 解析响应头中的 Content-Length 长度信息(v2.0.0 新增)
  • getBody() 得到响应报文体Body实例,它定义了如下方法(由于Body实例是一种一次性报文体,所以对于同一个Body实例,默认 以下的 toXXX() 系列方法只能 使用一个 且仅能 调用一次,除非先调用 它的 cache() 方法):
    • toBytes() 报文体转换为字节数组
    • toByteStream() 报文体转换为字节输入流
    • toCharStream() 报文体转换为字符输入流
    • toString() 报文体转换为字符串文本
    • toBean(Class<T> type) 报文体根据type自动反序列化为 JavaBean(需要配置 MsgConvertor
    • toBean(Type type) 报文体根据type自动反序列化为 JavaBean(需要配置 MsgConvertor)(支持复合泛型)
    • toBean(TypeRef<T> type) 报文体根据type自动反序列化为 JavaBean(需要配置 MsgConvertor)(支持复合泛型)
    • toList(Class<T> type) 报文体根据type自动反序列化为 JavaBean 列表(需要配置 MsgConvertor
    • toMapper() 报文体自动反序列化为映射结构的对象(需要配置 MsgConvertor,v2.0.0.RC 之前是toJsonObject()
    • toArray() 报文体自动反序列化为数组结构的对象(需要配置 MsgConvertor,v2.0.0.RC 之前是toJsonArray()
    • toFile(String filePath) 下载到指定路径
    • toFile(File file) 下载到指定文件
    • toFolder(String dirPath) 下载到指定目录
    • toFolder(File dir) 下载到指定目录
    • getType() 返回报文体的媒体类型(v2.0.0 新增,不再推荐getContentType()方法)
    • getLength() 返回报文体的字节长度(v2.0.0 新增,不再推荐getContentLength()方法)
    • cache() 缓存报文体,开启缓存后可 重复 使用 toXXX() 系列方法
    • close() 关闭报文体,未对报文体做任何消费时使用,比如只读取报文头
  • getError() 执行中发生的异常,自动捕获执行请求时发生的 网络超时、网络错误 和 其它IO异常
  • close() 关闭报文,未对报文体做任何消费时使用,比如只读取长度

示例,请求结果自动反序列化为 Bean 和 List:

// 自动转Bean
Order order = http.sync("/orders/1").get()
        .getBody().toBean(Order.class);

// 自动转List
List<Order> orders = http.sync("/orders")
        .get().getBody().toList(Order.class);
1
2
3
4
5
6
7

上例中,为了获取订单列表,必须先定义一个Order类,如果你不想定义它,可以直接使用toMapper()toArray()方法:

Mapper order = http.sync("/orders/1")
        .get().getBody().toMapper();

long orderId = order.getLong("id");         // 订单ID
long orderNo = order.getString("orderNo");  // 订单号

Array orders = http.sync("/orders")
        .get().getBody().toArray();

int size = orders.size();                   // 订单个数
Mapper o1 = orders.getMapper(0)             // 第1个订单
1
2
3
4
5
6
7
8
9
10
11

关于 Mapper 和 Array 的更多信息,请参考 Mapper 接口Array 接口

示例,使用 cache() 方法:

Body body = http.sync("/orders").get().getBody().cache();

// 使用 cache 后,可以多次使用 toXXX() 方法
System.out.println(body.toString());
System.out.println(body.toArray());
System.out.println(body.toList(Order.class));
1
2
3
4
5
6

示例,获取下载文件的大小:

long size = http.sync("/download/test.zip").get().getBody()
            .close()        // 只是想获得文件大小,不消费报文体,所以直接关闭
            .getLength();   // 获得待下载文件的大小
// 由于未消费报文体,所以本次请求不会消耗下载报文体的时间和网络流量
System.out.println("size = " + size);
1
2
3
4
5

上述代码等同于:

long size = http.sync("/download/test.zip").get().close().getContentLength(); 
System.out.println("size = " + size);
1
2

还等效于:

long size = http.sync("/download/test.zip").head().getContentLength(); 
// 因为 HEAD 请求没有响应报文体,所以就不需要关闭啦
System.out.println("size = " + size);
1
2
3

调式注意事项

由于HttpResult#BodytoString()方法也是消费报文体的一种方式,并且调用一次后默认不能再调用下一个toXXX()类的方法。但是在调式模式下,调式工具经常会自动调用对象的toString()方法,所以这点应当特别注意,例如下面这段代码:

http.async("/api/....")
    .setOnResponse((HttpResult res) -> {
        Body body = res.getBody();              // ①
        User user = body.toBean(User.class);    // ②
        System.out.println(user);               // ③
    })
1
2
3
4
5
6

对于以上的代码,如果如果我们在第 ② 处打断点,那么某些 IDE 会自动调用body对象的toString()方法,从而消费掉报文体,所以当再往下走执行toBean()方法时,报文体便已经不存在了,所以也就得不到期望的结果了。

所以对于以上的代码,如果想要调式查看user对象,可以直接断点到 ③ 处,并且鼠标不要悬停在body对象上。

但是如果我们就是需要调式查看body对象,该怎么办呢?我们可以预先使用cache()方法,修改代码如下:

http.async("/api/....")
    .setOnResponse((HttpResult res) -> {
        Body body = res.getBody().cache();      // ①
        User user = body.toBean(User.class);    // ②
        System.out.println(user);               // ③
    })
1
2
3
4
5
6

我们在 ① 处使用了cache()方法,这使得报文体可以被重复消费,此时当我们再次断点在 ② 处时,便可以调式查看body对象了,因为在此之前已经执行了cache()方法从而使得报文体不再是一次性的了。

HttpCall

HttpCall是异步请求方法get()post()等的返回值,它是一个接口,继承自Cancelable,与javaFuture接口很像,它有如下方法:

  • cancel() 取消本次请求,返回取消结果
  • isCanceled() 返回请求是否被取消
  • isDone() 返回是否执行完成,包含取消和失败
  • getResult() 返回执行结果HttpResult对象,若请求未执行完,则挂起当前线程直到执行完成再返回

例如,取消一个异步请求:

HttpCall call = http.async("/users/1").get();

System.out.println(call.isCanceled());     // false

boolean success = call.cancel();           // 取消请求

System.out.println(success);               // true
System.out.println(call.isCanceled());     // true
1
2
3
4
5
6
7
8

构建请求

HTTP接口的sync(String url)async(String url)webSocket(String url)方法返回一个HttpTask对象,它实现了Cancelable接口,同时也提供了可链式调用的addXXXsetXXX等系列方法用于构建请求报文。

请求头

  • addHeader(String name, String value) 添加请求头
  • addHeader(Map<String, String> headers) 添加请求头

Content-Type 请求头

这是一个比较特殊的请求头,它用于指定请求报文体的类型(如:JSON请求、表单请求、XML请求 等)。所以这个请求头,有一个专用的设定方法:

  • bodyType(String type) -- 设定 Content-Type 请求头

它接受一个 String 类型的参数 type,它可以是一个完整的 Content-Type 值,如:

http.sync('https://...').bodyType("application/json")...
http.sync('https://...').bodyType("application/xml")...
http.sync('https://...').bodyType("application/x-www-form-urlencoded")...
1
2
3

也可以是如下的缩写形式:

http.sync('https://...').bodyType("json")...
http.sync('https://...').bodyType("xml")...
http.sync('https://...').bodyType("form")...
1
2
3

提示

  • 进行表单请求时,若以全称 application/x-www-form-urlencoded 指定 bodyType 或 调用 setBodyPara(Object body) 方法且传参为待序列化的 POJO 时,你还需要为 HTTP 实例配置一个 表单转换器,否则在发起表单请求时,会报 没有匹配[application/x-www-form-urlencoded]类型的转换器 异常。
    v2.4.6 起,已优化为:仅在 调用 setBodyPara(Object body) 方法时且传参为待序列化的 POJO 时,才需要为 HTTP 实例配置 表单转换器
  • 当进行其它类型的请求时,当报文体需要被序列化时,都需要添加对应的 消息转换器

更多参加 报文体类型 章节。

路径参数

  • addPathPara(String name, Object value) 添加路径参数:替换 URL 里的{name}占位符
  • addPathPara(Map<String, ?> params) 添加路径参数:替换 URL 里的{name}占位符

路径参数可以存在于 URL 中的任何位置,例如:

final String BOOKS_QUERY_URL = "/authors/{authorId}/books?type={type}";

http.async(BOOKS_QUERY_URL)     // /authors/1/books?bookType=2
        .addPathPara("authorId", 1)
        .addPathPara("type", 2)
        .setOnResponse((HttpResult res) -> {

        })
        .get();
1
2
3
4
5
6
7
8
9

查询参数

  • addUrlPara(String name, Object value) 添加 URL 参数:拼接在 URL 的?之后(查询参数)
  • addUrlPara(Map<String, ?> params) 添加 URL 参数:拼接在 URL 的?之后(查询参数)

WARNING

方法addXxxPara在 v2.0.0.RC 之前名为addXxxParam, v2.0.0.RC 之后推荐使用简洁版的方法,老方法已在 v2.1.0 中移除。

报文体类型

  • bodyType(String type) 结合报文体参数使用,用于设置本次的请求报文体类型,可以是formjsonxmlprotobuf等,默认为form(表单类型),可以在构建HTTP实例时修改默认的类型

解析

bodyType对应于MsgConvertormediaType()方法的返回值,OkHttps 会按照既定的规则 [mediaType().contain(bodyType)] 去匹配出对应的MsgConvertor去序列化报文体参数。

报文体参数(表单、JSON、XML等)

OkHttps 信仰统一与一致更加优雅,所以自 v2.0.0.RC 开始、它便统一了报文体参数的 API,无论是表单、还是 JSON、XML 或是 protobuf 等等,只要这些参数是放在请求报文体中,那么就可以通过一套 API 轻松搞定!

  • addBodyPara(String name, Object value) 添加 Body 参数(只支持单层结构,v3.4 开始支持多层结构
  • addBodyPara(Map<String, ?> params) 添加 Body 参数(只支持单层结构,v3.4 开始支持多层结构
  • setBodyPara(Object object) 设置 Body 参数体(支持多层结构

注意

添加报文体参数后,不可使用 GET、HEAD 请求方法

方法setBodyPara在 v2.0.0.RC 之前名为setRequestJson, v2.0.0.RC 之后推荐使用统一 API,老方法已在 v2.1.0 中移除。

表单请求

默认的请求报文体类型就是form表单,所以可以直接使用addBodyPara方法:

http.async("/projects") 
        .addBodyPara("name", "OkHttps")
        .addBodyPara("desc", "最好用的网络框架")
        .post();
1
2
3
4

或者是传入一个 Map 对象:

Map<String, Object> params = new HashMap<>();
params.put("name", "OkHttps");
params.put("desc", "最好用的网络框架");

http.async("/projects")
        .addBodyPara(params)
        .post();  
1
2
3
4
5
6
7

甚至可以用setBodyPara传入一个String

http.async("/projects") 
        .setBodyPara("name=OkHttps&desc=最好用的网络框架")
        .post();  
1
2
3

如果你配置了FormConvertor(请参考 配置-表单序列化 章节),那你还可以直接传入一个 POJO(自定义的一个 Java 类):

Proejct project = new Proejct();
project.setName("OkHttps");
project.setDesc("最好用的网络框架");

http.async("/projects") 
        .setBodyPara(project)       // 将自动序列化为表单格式
        .post();
1
2
3
4
5
6
7

以上 4 种方式具有相同的效果,但如果你修改了默认的报文体序列化类型(请参考 默认序列化类型 章节),那还需在请求时指定当前的请求报文体类型:

http.async("/projects") 
        .bodyType("form")           // 指明请求体类型是表单
        ...
1
2
3

或使用OkHttps.FORM常量:

http.async("/projects") 
        .bodyType(OkHttps.FORM)     // 指明请求体类型是表单
        ...
1
2
3

JSON 请求

JSON 请求要求默认的请求bodyTypejson 或者 在具体请求中显式指明bodyTypejson,其它用法和表单请求一模一样。

单个添加(需要配置解析 JSON 的 MsgConvertor):

http.async("/projects") 
        .addBodyPara("name", "OkHttps")
        .addBodyPara("desc", "最好用的网络框架")
        .post();
1
2
3
4

Map 方式(需要配置解析 JSON 的 MsgConvertor):

Map<String, Object> params = new HashMap<>();
params.put("name", "OkHttps");
params.put("desc", "最好用的网络框架");

http.async("/projects") 
        .addBodyPara(params)
        .post();  
1
2
3
4
5
6
7

POJO 方式(需要配置解析 JSON 的 MsgConvertor):

Proejct project = new Proejct();
project.setName("OkHttps");
project.setDesc("最好用的网络框架");

http.async("/projects") 
        .setBodyPara(project)       // 自动序列化
        .post();   
1
2
3
4
5
6
7

字符串方式(v2.4.5 及之前版本 需要配置解析 JSON 的 MsgConvertor):

http.async("/projects") 
        .setBodyPara("{\"name\":\"OkHttps\",\"desc\":\"最好用的网络框架\"}")
        .post();  
1
2
3

唯一的不同是,如果默认的bodyType不是json,那需要显式指定当前请求的bodyTypejson

http.async("/projects") 
        .bodyType("json")           // 指明请求体类型是 JSON
        ...
1
2
3

或使用OkHttps.JSON常量:

http.async("/projects") 
        .bodyType(OkHttps.JSON)     // 指明请求体类型是 JSON
        ...
1
2
3

XML 请求

若默认不是 XML,则显式指定当前请求的报文体类型:

http.async("/projects") 
        .bodyType("xml")           // 指明请求体类型是 XML
        ...
1
2
3

或使用OkHttps.XML常量:

http.async("/projects") 
        .bodyType(OkHttps.XML)     // 指明请求体类型是 XML
        ...
1
2
3

其它与 表单和 JSON,完全一致,不再赘述。

其它数据格式的请求

只需要扩展了相应的MsgConvertor,OkHttps 支持任意数据格式的请求。

文件参数

文件参数是一个 特殊 的报文体参数。 当使用以下方法上传文件时,OkHttps 会忽略 bodyType 配置,而强制报文体使用 multipart/form-data 模式(但自 v3.4.2 起,如果 bodyTypemultipart/ 开头则不会被忽略)

  • addFilePara(String name, String filePath) 上传文件
  • addFilePara(String name, File file) 上传文件
  • addFilePara(String name, String type, byte[] content) 上传文件
  • addFilePara(String name, String type, String fileName, byte[] content) 上传文件

文件参数和报文体参数,还可以混用,例如:

http.sync("/upload")
        .addBodyPara("title", "头像")
        .addFilePara("image", "D:/image/avatar.jpg")
        .post();
1
2
3
4

WARNING

如果你使用的是 v2.0.0.RC 版本,以上代码会报 “方法 addFilePara 只能使用 form 方式请求” 错误,可以用以下方式解决该问题:

http.async("/upload")
        .bodyType("multipart/form")
        .addBodyPara("title", "头像")
        .addFilePara("test", "D:/image/avatar.jpg")
        .post();
1
2
3
4
5

详见 ISSUE: https://gitee.com/troyzhxu/okhttps/issues/I1H8G9

无论当前的默认bodyType是什么,和文件参数一起添加的Body参数都将以form(表单)模式提交。

其它

  • tag(String tag) 为 HTTP 任务添加标签
  • setRange(long rangeStart) 设置 Range 头信息,addHeader的便捷方法,用于断点续传
  • setRange(long rangeStart, long rangeEnd) 设置 Range 头信息,可用于分块下载
  • bind(Object object) 绑定一个对象,可用于实现 Android 里的生命周期绑定
  • nothrow() 一种发生异常时的处理方式,请参考 异常处理 章节
  • skipPreproc() 设置本次请求跳过所有预处理器,请参考 预处理器 章节
  • skipSerialPreproc() 设置本次请求只跳过所有串行预处理器,请参考 串行预处理器 章节

使用标签

有时候我们想对 HTTP 任务加以分类,这时候可以使用标签功能:

http.async("/users")    //(1)
        .tag("A")
        .get();
        
http.async("/users")    //(2)
        .tag("A.B")
        .get();
        
http.async("/users")    //(3)
        .tag("B")
        .get();
        
http.async("/users")    //(4)
        .tag("B")
        .tag("C")       // 从 v1.0.4 标签将以追加模式添加,等效于 tag("B.C")
        .get();
        
http.async("/users")    //(5)
        .tag("C")
        .get();

http.webSocket("/websocket")
        .tag("B")       // (6) 标签同样可以作用在 WebSocket 连接上
        .listen();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

预处理器中访问标签

预处理器 中可以通过chain对象获得当前的请求任务HttpTask,然后使用isTaggedgetTag方法获取标签信息:

HTTP http = HTTP.builder()
        // 添加预处理器
        .addPreprocessor(chain -> {

            // 获得当前的HTTP任务
            HttpTask<?> task = chain.getTask();
            // 判断该任务是否添加了 "A" 标签
            boolean tagged = task.isTagged("A");
            // 得到整个标签串
            String tag = task.getTag();
            
            // ...
        })
        // ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14

串行预处理器 中也是同样的方法访问标签。

全局监听中访问标签

全局监听 中,访问标签就加简单一点,因为HttpTask正是形参之一,例如全局响应监听:

HTTP http = HTTP.builder()
        // 全局响应监听
        .responseListener((HttpTask<?> task, HttpResult result) -> {

            // 判断该任务是否添加了 "A" 标签
            boolean tagged = task.isTagged("A");
            // 得到整个标签串
            String tag = task.getTag();

            // ...
        })
        // ...
1
2
3
4
5
6
7
8
9
10
11
12

其它类型的全局监听也是同样的方法访问标签。

拦截器中访问标签

自 v2.0.1 起,OkHttps 支持在拦截器内访问标签,可通过Request对象拿到整个标签串:

HTTP http = HTTP.builder()
        // OkHttpClient 原生配置
        .config(b -> {
            // 添加拦截器
            b.addInterceptor(chain -> {

                // 拿到 Request 对象
                Request request = chain.request();
                // 拿到整个标签串
                String tag = request.tag(String.class);
                
                // ...
            });
        })
        // ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

拦截器的配置,可参见 配置 OkHttpClient 章节。

使用标签取消请求

当使用标签后,还可以按标签批量的对HTTP任务进行取消:

int count = http.cancel("B");  //(2)(3)(4)(6)被取消(取消标签包含"B"的任务)
System.out.println(count);     // 输出 4
1
2

取消请求任务,只是标签的一个附带功能。

标签 真正的强大之处在于:它可以和 预处理器全局监听 及 拦截器 配合使用,以此来扩展很多功能。可参考 串行预处理器(token问题最佳解决方案)安卓-自动加载框 等章节。

另外,请求任务的取消,还有更多的方式,可参考 取消请求 章节

异常处理

使用 OkHttps 时,异常处理不是必须的,但相比其它的 HTTP 开发包,它还提供一个特别的处理方法:nothrow(),以满足不同的异常处理需求。

同步请求的异常

默认情况下,当同步请求执行异常时,会直接向外抛出,我们可以用 try catch 来捕获,例如:

try {
    HttpResult result = http.sync("/users/1").get();
} catch (HttpException e) {
    Throwable cause = e.getCause(); // 得到异常原因
    if (cause instanceof ConnectException) {
        // 当没网络时,会抛出连接异常
    }
    if (cause instanceof SocketTimeoutException) {
        // 当接口长时间未响应,会抛出超时异常
    }
    if (cause instanceof UnknownHostException) {
        // 当把域名或IP写错,会抛出 UnknownHost 异常
    }
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这种传统的异常处理方式,当然可以解决问题,但 OkHttps 有更佳的方案:

// 方法  nothrow() 让异常不直接抛出
HttpResult result = http.sync("/users/1").nothrow().get();
// 判断执行状态
switch (result.getState()) {
    case RESPONSED:     // 请求已正常响应
        break;
    case CANCELED:      // 请求已被取消
        break;
    case NETWORK_ERROR: // 网络错误,说明用户没网了
        break;
    case TIMEOUT:       // 请求超时
        break;
    case EXCEPTION:     // 其它异常
        break;
}
// 还可以获得具体的异常信息
IOException error = result.getError();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

异步请求的异常

异步请求最常用的异常处理方式就是设置一个异常回调:

http.async("/users/1")
        .setOnResponse((HttpResult result) -> {
            // 当发生异常时就不会走这里
        })
        .setOnException((IOException e) -> {
            // 这里处理请求异常
        })
        .get();
1
2
3
4
5
6
7
8

当然,还有一个全局异常监听(ExceptionListener,请参考 全局回调监听 章节):

HTTP http = HTTP.builder()
        .exceptionListener((HttpTask<?> task, IOException error) -> {
            // 所有请求执发生异常后都会走这里

            // 返回 true 表示继续执行 task 的 OnException 回调,
            // 返回 false 则表示不再执行,即 阻断
            return true;
        })
        .build();
1
2
3
4
5
6
7
8
9

如果不设置OnException回调,也没有ExceptionListener,发生异常时会在 IO 线程 中向上抛出,外层无法捕获:

try {
    http.async("/users/1")
            .setOnResponse((HttpResult result) -> {
                // 当发生异常时就不会走这里
            })
            .get();
} catch (HttpException e) {
    // 这种方式是捕获不到异常的!!!!!!
}
1
2
3
4
5
6
7
8
9

即使没有OnException回调,发生异常时,依然会走OnComplete回调,如果设置了的话:

http.async("/users/1")
        .setOnResponse((HttpResult result) -> {
            // 当发生异常时就不会走这里
        })
        .setOnComplete((State state) -> {
            // 发生异常,会先执行这里,可以根据 state 判断发生了什么
            // 但执行完后依然会在IO线程中向上抛出
        })
        .get();
1
2
3
4
5
6
7
8
9

如果就是想 不处理异常,也不向上抛出,发生错误完全无视,可以做到吗?可以!还是使用nothrow()方法:

http.async("/users/1")
        .nothrow()  // 告诉 OkHttps 发生异常时不向外抛出
        .setOnResponse((HttpResult result) -> {
            // 当发生异常时就不会走这里
        })
        .get();
1
2
3
4
5
6

WARNING

如果你开发的是安卓应用,我们强烈建议你添加 全局异常监听,这样当你在某个请求中忘记使用OnExceptionnothrow,而它又发生了超时或网络异常时,不至于让程序崩溃。

取消请求

在 OkHttps 里取消请求共有 4 种 方式可选:

1、 使用HttpCall#cancel()取消单个请求(适用于异步请求,详见HttpCall章节

2、 使用HttpTask#cancel()取消单个请求(适用于所有请求)(since v1.0.4)

HttpTask<?> task = http.async("/users")
        .setOnResponse((HttpResult result) -> {
            // 响应回调
        });

task.get(); // 发起 GET 请求

// 取消请求,并返回是否取消成功
boolean canceled = task.cancel();   
1
2
3
4
5
6
7
8
9

3、 使用HTTP#cancel(String tag)按标签批量取消请求(适用于所有请求,详见 标签 章节

4、 使用HTTP#cancelAll()取消所有请求(适用于所有请求)(since v1.0.2)

http.cancelAll();   // 取消所有请求
1

提示

以上四种方式都对所有类型的请求有效,包括:同步 HTTP、异步 HTTP 和 WebSocket 连接。

除了以上的 4 种方式,OkHttps 里还可以实现自动取消,请参考 安卓-生命周期绑定 章节。