官网 http://okhttps.ejlchina.com/
OkHttps 是近期开源的对 OkHttp3 轻量封装的框架,它独创的异步预处理器,特色的标签,灵活的上传下载进度监听与过程控制功能,在轻松解决很多原本另人头疼问题的同时,设计上也力求纯粹与优雅。
- 链式调用,一点到底
- BaseURL、URL占位符、JSON自动封装与解析
- 同步拦截器、异步预处理器、回调执行器、全局监听、回调阻断
- 文件上传下载(过程控制、进度监听)
- 单方法回调,充分利用 Lambda 表达式
- TCP连接池、Http2
- OkHttps 非常轻量(59Kb),是 Retrofit(124Kb)的一半,并且更加的开箱即用,API 也更加自然和语义化。
- OkHttps 是一个纯粹的 Java 网络开发包,并不依赖 Android,这一点和 Retrofit 不同
- OkHttps 用起来很优美,可以像 RxJava 那样链式用,却比 RxJava 更简单。
- 安装教程
- 使用说明
- 后期计划(v1.1.0)
- 联系方式
- 参与贡献
<dependency>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
<version>1.0.5</version>
</dependency>
implementation 'com.ejlchina:okhttps:1.0.5'
安卓中使用需要把 JDK 版本调成 1.8,在 app 模块的 build.gradle 中加入以下配置即可:
android {
// 省略其它配置..
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
}
HTTP http = HTTP.builder().build();
以上代码构建了一个最简单的HTTP
实例,它拥有以下方法:
async(String url)
开始一个异步请求 (内部通过一个HTTP
单例实现)async()
开始一个异步HTTP任务,使用该方法必须在构建时设置 BaseUrlsync(String url)
开始一个同步请求 (内部通过一个HTTP
单例实现)sync()
开始一个同步HTTP任务,使用该方法必须在构建时设置 BaseUrlcancel(String tag)
按标签取消请求(内部通过一个HTTP
单例实现)cancelAll()
取消所有HTTP任务,包括同步和异步(内部通过一个HTTP
单例实现)request(Request request)
OkHttp 原生请求 (该请求不经过 预处理器)webSocket(Request request, WebSocketListener listener)
WebSocket通讯
为了使用方便,在构建的时候,我们更愿意指定一个BaseUrl
(请参见5.1 设置 BaseUrl):
HTTP http = HTTP.builder()
.baseUrl("http://api.demo.com")
.build();
为了简化文档,下文中出现的http
均是在构建时设置了BaseUrl
的HTTP
实例。
使用方法sync(String url)
开始一个同步请求:
List<User> users = http.sync("/users") // http://api.demo.com/users
.get() // GET请求
.getBody() // 获取响应报文体
.toList(User.class); // 得到目标数据
方法sync
返回一个同步HttpTask
,可链式使用。
使用方法async(String url)
开始一个异步请求:
http.async("/users/1") // http://api.demo.com/users/1
.setOnResponse((HttpResult result) -> {
// 得到目标数据
User user = result.getBody().toBean(User.class);
})
.get(); // GET请求
方法async
返回一个异步HttpTask
,可链式使用。
同步与异步的HttpTask
都拥有get
、post
、put
与delete
方法。不同的是:同步HttpTask
的这些方法返回一个HttpResult
,而异步HttpTask
的这些方法返回一个HttpCall
。
HttpResult res1 = http.sync("/users").get(); // 同步 GET
HttpResult res2 = http.sync("/users").post(); // 同步 POST
HttpResult res3 = http.sync("/users/1").put(); // 同步 PUT
HttpResult res4 = http.sync("/users/1").delete();// 同步 DELETE
HttpCall call1 = http.async("/users").get(); // 异步 GET
HttpCall call2 = http.async("/users").post(); // 异步 POST
HttpCall call3 = http.async("/users/1").put(); // 异步 PUT
HttpCall call4 = http.async("/users/1").delete();// 异步 DELETE
OkHttps 的回调函数全部使用单方法模式,这样可以充分利用 Java8 或 Kotlin 中的 Lambda 表达式,使你的代码更加简洁优雅:
http.async("/users/{id}") // http://api.demo.com/users/1
.addPathParam("id", 1)
.setOnResponse((HttpResult result) -> {
// 响应回调
})
.setOnException((IOException e) -> {
// 异常回调
})
.setOnComplete((State state) -> {
// 完成回调,无论成功失败都会执行
// 并且在 响应|异常回调 之前执行
})
.get();
- 只有异步请求才可以设置这三种(响应|异常|完成)回调
- 同步请求直接返回结果,无需使用回调
HttpResult
是HTTP请求执行完后的结果,它是同步请求方法( get
、post
、put
、delete
)的返回值,也是异步请求响应回调(OnResponse
)的参数,它定义了如下方法:
getState()
得到请求执行状态枚举,它有以下取值:State.CANCELED
请求被取消State.RESPONSED
已收到响应State.TIMEOUT
请求超时State.NETWORK_ERROR
网络错误State.EXCEPTION
其它请求异常
getStatus()
得到HTTP状态码isSuccessful()
是否响应成功,状态码在 [200..300) 之间getHeaders()
得到HTTP响应头getHeaders(String name)
得到HTTP响应头getHeader(String name)
得到HTTP响应头getBody()
得到响应报文体Body
实例,它定义了如下方法(对同一个Body
实例,以下的toXXX()
类方法只能使用一个且仅能调用一次,除非先使用 cache 方法):toBytes()
返回字节数组toByteStream()
返回字节输入流toCharStream()
返回字符输入流toString()
返回字符串toJsonObject()
返回Json对象toJsonArray()
返回Json数组toBean(Class<T> type)
返回根据type自动json解析后的JavaBeantoList(Class<T> type)
返回根据type自动json解析后的JavaBean列表toFile(String filePath)
下载到指定路径toFile(File file)
下载到指定文件toFolder(String dirPath)
下载到指定目录toFolder(File dir)
下载到指定目录getContentType()
返回报文体的媒体类型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);
示例,使用 cache 方法:
Body body = http.sync("/orders").get().getBody().cache();
// 使用 cache 后,可以多次使用 toXXX() 方法
System.out.println(body.toString());
System.out.println(body.toJsonArray());
System.out.println(body.toList(Order.class));
示例,获取下载文件的大小:
long size = http.sync("/download/test.zip")
.get().getBody()
.close() // 只是想获得文件大小,不消费报文体,所以直接关闭
.getContentLength(); // 获得待下载文件的大小
// 由于未消费报文体,所以本次请求不会消耗下载报文体的时间和网络流量
System.out.println("size = " + size);
HttpCall
对象是异步请求方法(get
、post
、put
、delete
)的返回值,与java
的Future
接口很像,它有如下方法:
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
HTTP
对象的sync
与async
方法返回一个HttpTask
对象,该对象提供了可链式调用的addXXX
与setXXX
等系列方法用于构建任务本身。
-
addHeader(String name, String value)
添加请求头 -
addHeader(Map<String, String> headers)
添加请求头 -
addPathParam(String name, Object value)
添加路径参数:替换URL里的{name}占位符 -
addPathParam(Map<String, ?> params)
添加路径参数:替换URL里的{name}占位符 -
addUrlParam(String name, Object value)
添加URL参数:拼接在URL的?之后(查询参数) -
addUrlParam(Map<String, ?> params)
添加URL参数:拼接在URL的?之后(查询参数) -
addBodyParam(String name, Object value)
添加Body参数:以表单key=value&的形式放在报文体内(表单参数) -
addBodyParam(Map<String, ?> params)
添加Body参数:以表单key=value&的形式放在报文体内(表单参数) -
addJsonParam(String name, Object value)
添加Json参数:请求体为Json(支持多层结构) -
addJsonParam(Map<String, ?> params)
添加Json参数:请求体为Json(支持多层结构) -
setRequestJson(Object json)
设置请求体的Json字符串 或待转换为 Json的 JavaBean -
setRequestJson(Object bean, String dateFormat)
设置请求体的Json字符串 或待转换为 Json的 JavaBean -
addFileParam(String name, String filePath)
上传文件 -
addFileParam(String name, File file)
上传文件 -
addFileParam(String name, String type, InputStream inputStream)
上传文件 -
addFileParam(String name, String type, String fileName, InputStream input)
上传文件 -
addFileParam(String name, String type, byte[] content)
上传文件 -
addFileParam(String name, String type, String fileName, byte[] content)
上传文件 -
setTag(String tag)
为HTTP任务添加标签 -
setRange(long rangeStart)
设置Range头信息,用于断点续传 -
setRange(long rangeStart, long rangeEnd)
设置Range头信息,可用于分块下载 -
bind(Object object)
绑定一个对象,可用于实现Android里的生命周期绑定
有时候我们想对HTTP任务加以分类,这时候可以使用标签功能:
http.async("/users") //(1)
.setTag("A")
.get();
http.async("/users") //(2)
.setTag("A.B")
.get();
http.async("/users") //(3)
.setTag("B")
.get();
http.async("/users") //(4)
.setTag("B")
.setTag("C") // 从 v1.0.4 标签将以追加模式添加,等效于 setTag("B.C")
.get();
http.async("/users") //(5)
.setTag("C")
.get();
当使用标签后,就可以按标签批量的对HTTP任务进行取消:
int count = http.cancel("B"); //(2)(3)(4)被取消(取消标签包含"B"的任务)
System.out.println(count); // 输出 3
标签除了可以用来取消任务,在预处理器中它也可以发挥作用,请参见并行预处理器与串行预处理器。
HTTP http = HTTP.builder()
.baseUrl("http://api.demo.com") // 设置 BaseUrl
.build();
该配置全局生效,在配置了BaseUrl
之后,具体的请求便可以省略BaseUrl
部分,使得代码更加简洁,例如:
http.sync("/users").get() // http://api.demo.com/users
http.sync("/auth/signin") // http://api.demo.com/auth/signin
.addBodyParam("username", "Jackson")
.addBodyParam("password", "xxxxxx")
.post() // POST请求
在配置了BaseUrl
之后,如有特殊请求任务,仍然可以使用全路径的方式,一点都不妨碍:
http.sync("https://www.baidu.com").get()
OkHttps 默认所有回调都在 IO线程 执行,如何想改变执行回调的线程时,可以配置回调执行器。例如在Android里,让所有的回调函数都在UI线程执行,则可以在构建HTTP
时配置如下:
HTTP http = HTTP.builder()
.callbackExecutor((Runnable run) -> {
runOnUiThread(run); // 在UI线程执行
})
.build();
该配置默认 影响所有回调。
与其他封装 OkHttp3 的框架不同,OkHttps 并不会遮蔽 OkHttp3 本身就很好用的功能,如下:
HTTP http = HTTP.builder()
.config((Builder builder) -> {
// 配置连接池 最小10个连接(不配置默认为 5)
builder.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES));
// 配置连接超时时间(默认10秒)
builder.connectTimeout(20, TimeUnit.SECONDS);
// 配置拦截器
builder.addInterceptor((Chain chain) -> {
Request request = chain.request();
// 必须同步返回,拦截器内无法执行异步操作
return chain.proceed(request);
});
// 其它配置: CookieJar、SSL、缓存、代理、事件监听...
})
.build();
预处理器(Preprocessor
)可以让我们在请求发出之前对请求本身做一些改变,但与OkHttp
的拦截器(Interceptor
)不同:预处理器可以让我们 异步 处理这些问题。
例如,当我们想为请求任务自动添加Token
头信息,而Token
只能通过异步方法requestToken
获取时,这时使用Interceptor
就很难处理了,但可以使用预处理器轻松解决:
HTTP http = HTTP.builder()
.addPreprocessor((PreChain chain) -> {
HttpTask<?> task = chain.getTask();// 获得当前的HTTP任务
if (!task.isTagged("Auth")) { // 根据标签判断该任务是否需要Token
return;
}
requestToken((String token) -> { // 异步获取 Token
task.addHeader("Token", token);// 为任务添加头信息
chain.proceed(); // 继续当前的任务
});
})
.build();
和Interceptor
一样,Preprocessor
也可以添加多个。他们之间的区别如下:
- 拦截器只能处理同步操作,预处理器支持处理异步操作
- 拦截器都是并行处理请求,预处理器支持串行处理(详见6.5章节)
- 拦截器处理时机在请求前和响应后,预处理器只在请求前,并且先于拦截器执行。关于响应后,OkHttps还提供了全局回调监听(详见6.6章节)
普通预处理器都是可并行处理的,然而有时我们希望某个预处理器同时只处理一个任务。比如 当Token
过期时我们需要去刷新获取新Token
,而刷新Token
这个操作只能有一个任务去执行,因为如果n
个任务同时执行的话,那么必有n-1
个任务刚刷新得到的Token
可能就立马失效了,而这是我们所不希望的。
为了解决这个问题,OkHttps 提供了串行预处理器,它可以让 HTTP 任务排好队,一个一个地进入预处理器:
HTTP http = HTTP.builder()
.addSerialPreprocessor((PreChain chain) -> {
HttpTask<?> task = chain.getTask();
if (!task.isTagged("Auth")) {
return;
}
// 检查过期,若需要则刷新Token
requestTokenAndRefreshIfExpired((String token) -> {
task.addHeader("Token", token);
chain.proceed(); // 调用此方法前,不会有其它任务进入该处理器
});
})
.build();
串行预处理器实现了让HTTP任务排队串行处理的功能,但值得一提的是:它并没有因此而阻塞任何线程!
HTTP http = HTTP.builder()
.responseListener((HttpTask<?> task, HttpResult result) -> {
// 所有请求响应后都会走这里
return true; // 返回 true 表示继续执行 task 的 OnResponse 回调,false 表示不再执行
})
.completeListener((HttpTask<?> task, State state) -> {
// 所有请求执行完都会走这里
return true; // 返回 true 表示继续执行 task 的 OnComplete 回调,false 表示不再执行
})
.exceptionListener((HttpTask<?> task, IOException error) -> {
// 所有请求发生异常都会走这里
return true; // 返回 true 表示继续执行 task 的 OnException 回调,false 表示不再执行
})
.build();
全局回调监听与拦截器的异同:
- 拦截器可以添加多个,全局回调监听分三种,每种最多添加一个
- 拦截器处的理时机在请求前和响应后,全局回调监听只在响应后,并且晚于拦截器
- 全局回调监听可以 阻断(return false)某个请求的具体回调,而拦截器不能
HTTP http = HTTP.builder()
.downloadListener((HttpTask<?> task, Download download) -> {
// 所有下载在开始之前都会先走这里
Ctrl ctrl = download.getCtrl(); // 下载控制器
})
.build();
类HttpUtils
本是 前身 HttpUtils 的 1.x 版本里的最重要的核心类,由于在后来的版本里抽象出了HTTP
接口,使得它的重要性已不如往昔。但合理的使用它,仍然可以带来不少便利,特别是在没有IOC容器的环境里,比如在Android开发和一些工具项目的开发中。
类HttpUtils
共定义了四个静态方法:
of(HTTP http)
配置HttpUtils
持有的HTTP
实例(不调用此方法前默认使用一个没有没有经过任何配置的HTTP
懒实例)async(String url)
开始一个异步请求 (内部通过一个HTTP
单例实现)async()
开始一个异步HTTP任务,使用该方法必须在构建时设置 BaseUrlsync(String url)
开始一个同步请求 (内部通过一个HTTP
单例实现)sync()
开始一个同步HTTP任务,使用该方法必须在构建时设置 BaseUrlcancel(String tag)
按标签取消请求(内部通过一个HTTP
单例实现)cancelAll()
取消所有HTTP任务,包括同步和异步(内部通过一个HTTP
单例实现)request(Request request)
OkHttp 原生请求 (该请求不经过 预处理器)webSocket(Request request, WebSocketListener listener)
WebSocket通讯 也就是说,能使用http
实例的地方,都可以使用HttpUtils
类,例如:
// 在配置HTTP实例之前,只能使用全路径方式
List<Role> roles = HttpUtils.sync("http://api.demo.com/roles")
.get().getBody().toList(Role.class);
// 配置HTTP实例,全局生效
HttpUtils.of(HTTP.builder()
.baseUrl("http://api.demo.com")
.build());
// 内部使用新的HTTP实例
List<User> users = HttpUtils.sync("/users")
.get().getBody().toList(User.class);
OkHttps 并没有把文件的下载排除在常规的请求之外,同一套API,它优雅的设计使得下载与常规请求融合的毫无违和感,一个最简单的示例:
http.sync("/download/test.zip")
.get() // 使用 GET 方法(其它方法也可以,看服务器支持)
.getBody() // 得到报文体
.toFile("D:/download/test.zip") // 下载到指定的路径
.start(); // 启动下载
http.sync("/download/test.zip").get().getBody()
.toFolder("D:/download") // 下载到指定的目录,文件名将根据下载信息自动生成
.start();
或使用异步连接方式:
http.async("/download/test.zip")
.setOnResponse((HttpResult result) -> {
result.getBody().toFolder("D:/download").start();
})
.get();
这里要说明一下:sync
与async
的区别在于连接服务器并得到响应这个过程的同步与异步(这个过程的耗时在大文件下载中占比极小),而start
方法启动的下载过程则都是异步的。
就直接上代码啦,诸君一看便懂:
http.sync("/download/test.zip")
.get()
.getBody()
.setStepBytes(1024) // 设置每接收 1024 个字节执行一次进度回调(不设置默认为 8192)
// .setStepRate(0.01) // 设置每接收 1% 执行一次进度回调(不设置以 StepBytes 为准)
.setOnProcess((Process process) -> { // 下载进度回调
long doneBytes = process.getDoneBytes(); // 已下载字节数
long totalBytes = process.getTotalBytes(); // 总共的字节数
double rate = process.getRate(); // 已下载的比例
boolean isDone = process.isDone(); // 是否下载完成
})
.toFolder("D:/download/") // 指定下载的目录,文件名将根据下载信息自动生成
// .toFile("D:/download/test.zip") // 指定下载的路径,若文件已存在则覆盖
.setOnSuccess((File file) -> { // 下载成功回调
})
.start();
值得一提的是:由于 OkHttps 并没有把下载做的很特别,这里设置的进度回调不只对下载文件起用作,即使对响应JSON的常规请求,只要设置了进度回调,它也会告诉你报文接收的进度(提前是服务器响应的报文有Content-Length
头),例如:
List<User> users = http.sync("/users")
.get()
.getBody()
.setStepBytes(2)
.setOnProcess((Process process) -> {
System.out.println(process.getRate());
})
.toList(User.class);
过于简单:还是直接上代码:
Ctrl ctrl = http.sync("/download/test.zip")
.get()
.getBody()
.setOnProcess((Process process) -> {
System.out.println(process.getRate());
})
.toFolder("D:/download/")
.start(); // 该方法返回一个下载过程控制器
ctrl.status(); // 下载状态
ctrl.pause(); // 暂停下载
ctrl.resume(); // 恢复下载
ctrl.cancel(); // 取消下载(同时会删除文件,不可恢复)
无论是同步还是异步发起的下载请求,都可以做以上的控制:
http.async("/download/test.zip")
.setOnResponse((HttpResult result) -> {
// 拿到下载控制器
Ctrl ctrl = result.getBody().toFolder("D:/download/").start();
})
.get();
OkHttps 对断点续传并没有再做更高层次的封装,因为这是app该去做的事情,它在设计上使各种网络问题的处理变简单的同时力求纯粹。下面的例子可以看到,OkHttps 通过一个失败回调拿到 断点,便将复杂的问题变得简单:
http.sync("/download/test.zip")
.get()
.getBody()
.toFolder("D:/download/")
.setOnFailure((Failure failure) -> { // 下载失败回调,以便接收诸如网络错误等失败信息
IOException e = failure.getException(); // 具体的异常信息
long doneBytes = failure.getDoneBytes(); // 已下载的字节数(断点),需要保存,用于断点续传
File file = failure.getFile(); // 下载生成的文件,需要保存 ,用于断点续传(只保存路径也可以)
})
.start();
下面代码实现续传:
long doneBytes = ... // 拿到保存的断点
File file = ... // 待续传的文件
http.sync("/download/test.zip")
.setRange(doneBytes) // 设置断点(已下载的字节数)
.get()
.getBody()
.toFile(file) // 下载到同一个文件里
.setAppended() // 开启文件追加模式
.setOnSuccess((File file) -> {
})
.setOnFailure((Failure failure) -> {
})
.start();
当文件很大时,有时候我们会考虑分块下载,与断点续传的思路是一样的,示例代码:
static String url = "http://api.demo.com/download/test.zip"
public static void main(String[] args) {
long totalSize = HttpUtils.sync(url).get().getBody()
.close() // 因为这次请求只是为了获得文件大小,不消费报文体,所以直接关闭
.getContentLength(); // 获得待下载文件的大小(由于未消费报文体,所以该请求不会消耗下载报文体的时间和网络流量)
download(totalSize, 0); // 从第 0 块开始下载
sleep(50000); // 等待下载完成(不然本例的主线程就结束啦)
}
static void download(long totalSize, int index) {
long size = 3 * 1024 * 1024; // 每块下载 3M
long start = index * size;
long end = Math.min(start + size, totalSize);
HttpUtils.sync(url)
.setRange(start, end) // 设置本次下载的范围
.get().getBody()
.toFile("D:/download/test.zip") // 下载到同一个文件里
.setAppended() // 开启文件追加模式
.setOnSuccess((File file) -> {
if (end < totalSize) { // 若未下载完,则继续下载下一块
download(totalSize, index + 1);
} else {
System.out.println("下载完成");
}
})
.start();
}
一个简单文件上传的示例:
http.sync("/upload")
.addFileParam("test", "D:/download/test.zip")
.post() // 上传发法一般使用 POST 或 PUT,看服务器支持
异步上传也是完全一样:
http.async("/upload")
.addFileParam("test", "D:/download/test.zip")
.post()
OkHttps 的上传进度监听,监听的是所有请求报文体的发送进度,示例代码:
http.sync("/upload")
.addBodyParam("name", "Jack")
.addBodyParam("age", 20)
.addFileParam("avatar", "D:/image/avatar.jpg")
.setStepBytes(1024) // 设置每发送 1024 个字节执行一次进度回调(不设置默认为 8192)
// .setStepRate(0.01) // 设置每发送 1% 执行一次进度回调(不设置以 StepBytes 为准)
.setOnProcess((Process process) -> { // 上传进度回调
long doneBytes = process.getDoneBytes(); // 已发送字节数
long totalBytes = process.getTotalBytes(); // 总共的字节数
double rate = process.getRate(); // 已发送的比例
boolean isDone = process.isDone(); // 是否发送完成
})
.post()
咦!怎么感觉和下载的进度回调的一样?没错!OkHttps 还是使用同一套API处理上传和下载的进度回调,区别只在于上传是在get/post
方法之前使用这些API,下载是在getBody
方法之后使用。很好理解:get/post
之前是准备发送请求时段,有上传的含义,而getBody
之后,已是报文响应的时段,当然是下载。
上传文件的过程控制就很简单,和常规请求一样,只有异步发起的上传可以取消:
HttpCall call = http.async("/upload")
.addFileParam("test", "D:/download/test.zip")
.setOnProcess((Process process) -> {
System.out.println(process.getRate());
})
.post()
call.cancel(); // 取消上传
上传就没有暂停和继续这个功能啦,应该没人有这个需求吧?
使用 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 异常
}
// ...
}
这种传统的异常处理方式,当然可以解决问题,但 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();
异步请求最常用的异常处理方式就是设置一个异常回调:
http.async("/users/1")
.setOnResponse((HttpResult result) -> {
// 当发生异常时就不会走这里
})
.setOnException((IOException e) -> {
// 这里处理请求异常
})
.get();
当然,还有一个全局异常监听(ExceptionListener
):
HTTP http = HTTP.builder()
.exceptionListener((HttpTask<?> task, IOException error) -> {
// 所有请求发生异常都会走这里
return true; // 返回 true 表示继续执行 task 的 OnException 回调,false 表示不再执行
})
.build();
如果不设置OnException
回调,也没有ExceptionListener
,发生异常时会在 IO 线程 中向上抛出,外层无法捕获:
try {
http.async("/users/1")
.setOnResponse((HttpResult result) -> {
// 当发生异常时就不会走这里
})
.get();
} catch (HttpException e) {
// 这种方式是捕获不到异常的!!!!!!
}
即使没有OnException
回调,发生异常时,依然会走OnComplete
回调,如果设置了的话:
http.async("/users/1")
.setOnResponse((HttpResult result) -> {
// 当发生异常时就不会走这里
})
.setOnComplete((State state) -> {
// 发生异常,会先执行这里,可以根据 state 判断发生了什么
// 但执行完后依然会在IO线程中向上抛出
})
.get();
如果就是想 不处理异常,也不向上抛出,发生错误完全无视,可以做到吗?可以!还是使用nothrow()
方法:
http.async("/users/1")
.nothrow() // 告诉 OkHttps 发生异常时不向外抛出
.setOnResponse((HttpResult result) -> {
// 当发生异常时就不会走这里
})
.get();
在 OkHttps 里取消请求共有 4 种 方式可选:
- 使用
HttpCall#cancel()
取消单个请求(适用于异步请求,详见 3.3 章节) - 使用
HttpTask#cancel()
取消单个请求(适用于所有请求)(since v1.0.4)
HttpTask<?> task = http.async("/users")
.setOnResponse((HttpResult result) -> {
// 响应回调
});
task.get(); // 发起 GET 请求
// 取消请求,并返回是否取消成功
boolean canceled = task.cancel();
- 使用
HTTP#cancel(String tag)
按标签批量取消请求(适用于所有请求,详见第 5 章节) - 使用
HTTP#cancelAll()
取消所有请求(适用于所有请求)(since v1.0.2)
http.cancelAll(); // 取消所有请求
在 Android 开发中,经常会把某些代码放到特点的线程去执行,比如网络请求响应后的页面更新在主线程(UI线程)执行,而保存文件则在IO线程操作。OkHttps 为这类问题提供了良好的方案。
在 默认 情况下,所有回调 函数都会 在 IO 线程 执行。为什么会设计如此呢?这是因为 OkHttps 只是纯粹的 Java 领域 Http工具包,本身对 Android 不会有任何依赖,因此也不知 Android 的 UI 线程为何物。这么设计也让它在 Android 之外有更多的可能性。
但是在 Android 里使用 OkHttps 的话,UI线程的问题能否优雅的解决呢?当然可以!简单粗暴的方法就是配置一个 回调执行器:
HTTP http = HTTP.builder()
.callbackExecutor((Runnable run) -> {
// 实际编码中可以吧 Handler 提出来,不需要每次执行回调都重新创建
new Handler(Looper.getMainLooper()).post(run); // 在主线程执行
})
.build();
上述代码便实现了让 所有 的 回调函数 都在 主线程(UI线程) 执行的目的,如:
http.async("/users")
.addBodyParam("name", "Jack")
.setOnProcess((Process process) -> {
// 在主线程执行
})
.setOnResponse((HttpResult result) -> {
// 在主线程执行
})
.setOnException((Exception e) -> {
// 在主线程执行
})
.setOnComplete((State state) -> {
// 在主线程执行
})
.post();
但是,如果同时还想让某些回调放在IO线程,实现 自由切换,怎么办呢?OkHttps 给出了非常灵活的方法,如下:
http.async("/users")
.addBodyParam("name", "Jack")
.setOnProcess((Process process) -> {
// 在主线程执行
})
.nextOnIO() // 指定下一个回调在 IO 线程执行
.setOnResponse((HttpResult result) -> {
// 在 IO 线程执行
})
.setOnException((Exception e) -> {
// 在主线程执行(没有指明 nextOnIO 则在回调执行器里执行)
})
.nextOnIO() // 指定下一个回调在 IO 线程执行
.setOnComplete((State state) -> {
// 在 IO 线程执行
})
.post();
无论是哪一个回调,都可以使用nextOnIO()
方法自由切换。同样,对于文件下载也是一样:
http.sync("/download/test.zip")
.get()
.getBody()
.setOnProcess((Process process) -> {
// 在主线程执行
})
.toFolder("D:/download/")
.nextOnIO() // 指定下一个回调在 IO 线程执行
.setOnSuccess((File file) -> {
// 在 IO 线程执行
})
.setOnFailure((Failure failure) -> {
// 在主线程执行
})
.start();
由于 OkHttps 并不依赖于 Android,所以它并没有提供关于生命周期绑定的直接实现,但它的一些扩展机制让我们很容易就可以实现这个需求。在开始之前,我们首先要理解何为生命周期绑定:
所谓的生命周期绑定:即是让 HTTP 任务感知其所属的 Activity 或 Fragment 的生命周期,当 Activity 或 Fragment 将被销毁时,框架应自动的把由它们发起的但尚未完成的 HTTP 任务全部取消,以免导致程序出错!
现在我们需要对HTTP
实例进行配置,配置后的HTTP
实例具有生命周期绑定的功能,在Activity
或Fragment
里,它的使用效果如下:
// 在 Activity 或 Fragment 内发起请求
http.async("http://www.baidu.com")
.bind(getLifecycle()) // 绑定生命周期
.setOnResponse((HttpResult result) -> {
Log.i("FirstFragment", "收到请求:" + result.toString());
})
.get();
上述代码中的getLifecycle()
是Activity
或Fragment
自带的方法,而bind()
是HttpTask
的现有方法。在配置好HTTP
实例后,上述代码发起的请求便可以感知Activity
或Fragment
的生命周期。
那HTTP
实例到底该如何配置呢?
HTTP http = HTTP.builder()
... // 省略其它配置项
.addPreprocessor((Preprocessor.PreChain chain) -> {
HttpTask<?> task = chain.getTask();
Object bound = task.getBound();
// 判断 task 是否绑定了 Lifecycle 对象
if (bound instanceof Lifecycle) {
// 重新绑定一个 生命周期监视器(LCObserver)对象,它的定义见下一步
task.bind(new LCObserver(task, (Lifecycle) bound));
}
chain.proceed();
})
... // 省略其它配置项
.build();
public class LCObserver implements LifecycleObserver {
HttpTask<?> task;
Lifecycle lifecycle;
LCObserver(HttpTask<?> task, Lifecycle lifecycle) {
this.task = task;
this.lifecycle = lifecycle;
lifecycle.addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop() {
task.cancel(); // 在 ON_STOP 事件中,取消对应的 HTTP 任务
}
public void unbind() {
// 在请求完成之后移除监视器
lifecycle.removeObserver(this);
}
}
以上两步其实已经实现了生命周期绑定的功能,但是在请求完成之后,我们需要在lifecycle
中移除LCObserver
对象:
HTTP http = HTTP.builder()
... // 省略其它配置项
.completeListener((HttpTask<?> task, HttpResult.State state) -> {
Object bound = task.getBound();
// 判断 task 是否绑定了生命周期监视器(LCObserver)对象
if (bound instanceof LCObserver) {
// 解绑监视器
((LCObserver) bound).unbind();
}
return true;
})
... // 省略其它配置项
.build();
以上三步便在Android中实现了生命周期与HTTP请求绑定的功能
非常简单,懒得敲代码的同学还可以 点这里 OkHttps.java 直接下载封装好的源码,其中不仅编写了生命周期绑定的配置,还有在UI线程执行回调的配置。
有需要的同学,可以直接下载下来使用,还可以基于它再次扩展,比如实现自动添加 TOKEN 的功能,具体可以参考6.5 串行预处理器(TOKEN问题最佳解决方案),再比如扩展实现生命周期与下载事件绑定的功能,都非常简单。
- 简化 WebSocket 编程:可直接发送 JavaBean 对象消息
- 简化 WebSocket 编程:可直接接收 JavaBean 对象消息
- 简化 WebSocket 编程:可使用 Lambda 表达式进行 WebSocket 编程
- 微信:
- 邮箱:[email protected]
- Fork 本仓库
- 新建 Feat_xxx 分支
- 提交代码
- 新建 Pull Request