OkHttp使用详解二
概要
上一篇笔记中已经简单介绍了OkHttp的基本使用,这一片继续介绍一些常用的功能,主要涉及请求缓存Cache、请求头Header、取消一个请求、如何信任所有的https、Dispatch以及如何在文件上传和下载时实时更新进度,当然了本篇笔记仍然仅仅涉及到如何使用,对于内部原理并没有做更多的深入。
响应的缓存处理
响应缓存主要设计到两个类的使用Cache和CacheControl,其中Cache主要是用于于请求成功后数据的缓存,CacheControl的使用如同我们在网页中设置http消息头中的“Cache-control”一样。
Cache
在OkHttp中Cache是有OkHttpClient设置进去的,为了使用Cache我们必须首先设置一个缓存目录和缓存大小,缓存目录应该是私有的,不信任的程序应不能读取缓存内容。 一个缓存目录同时拥有多个缓存访问是错误的。下面是一个简单的使用示例:
File dir = new File("cache"); long size = 1024 * 1024; Cache cache = new Cache(dir, size); OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();
CacheControl
CacheControl是在Request中设置的,响应缓存使用http头作为配置。可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600。
//Cache-Control: max-age=9600 new CacheControl.Builder().maxAge(9600, TimeUnit.SECONDS).build() //Cache-Control: max-stale=3600 new CacheControl.Builder().maxStale(3600, TimeUnit.SECONDS).build()
CacheControl中已经预先设置了两个常量FORCE_NETWORK和FORCE_CACHE,顾名思义一个是强制要求从网络获取数据另一个是强制从缓存获取数据,有一点需要注意,这是是强制,如果设置为FORCE_CACHE即使缓存中没有响应所需的数据或者缓存为空仍然不会转向网络请求,如果只设置了Cache而没有设置CacheControl,请求是会先从缓存获取响应信息,如果没有则会从网络重新获取响应信息。
下面是一个简单的请求示例:
String url = " http://192.168.0.117:8080/html/user.json"; File dir = new File("cache"); long size = 1024 * 1024; Cache cache = new Cache(dir, size); OkHttpClient client = new OkHttpClient.Builder().cache(cache).build(); Request request = new Request.Builder().url(url).build(); Response response = client.newCall(request).execute(); System.out.println("=============response cacheResponse:" + response.cacheResponse()); System.out.println("=============response networkResponse:" + response.networkResponse()); Response response2 = client.newCall(request).execute(); System.out.println("=============response2 cacheResponse:" + response2.cacheResponse()); System.out.println("=============response2 networkResponse:" + response2.networkResponse());
响应头Header的处理
Header在Http请求中用到的地方非常多,比如上面所讲的CacheControl也可以直接在Header中设置,还有上一篇中涉及到Cookie也可以直接在Header中直接设置,后面下一篇中讲解断点续下会用到一个属性range仍然是在Header设置的。我们知道典型的http请求头像是一个 Map<String, String>
:每个字段都有一个或没有值。但是一些头允许多个值,像Guava的Multimap。例如:HTTP响应里面提供的Vary响应头,就是多值的。OkHttp的api试图让这些情况都适用。
当写请求头的时候,使用header(name, value)可以设置唯一的name、value。如果已经有值,旧的将被移除,然后添加新的。使用addHeader(name, value)可以添加多值(添加,不移除已有的)。
当读取响应头时,使用header(name)返回最后出现的name、value。通常情况这也是唯一的name、value。如果没有值,那么header(name)将返回null。如果想读取字段对应的所有值,使用headers(name)会返回一个list。
为了获取所有的Header,Headers类支持按index访问。
//代码来自OkHttp官方demo public final class AccessHeaders { private final OkHttpClient client = new OkHttpClient(); public void run() throws Exception { Request request = new Request.Builder() .url("https://api.github.com/repos/square/okhttp/issues") .header("User-Agent", "OkHttp Headers.java") .addHeader("Accept", "application/json; q=0.5") .addHeader("Accept", "application/vnd.github.v3+json") .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); System.out.println("Server: " + response.header("Server")); System.out.println("Date: " + response.header("Date")); System.out.println("Vary: " + response.headers("Vary")); } public static void main(String... args) throws Exception { new AccessHeaders().run(); } }
取消一个请求
取消一个请求主要是通过Call中的cancel()方法,可以立即停止掉一个正在执行的call。如果一个线程正在写请求或者读响应,将会引发IOException。当call没有必要的时候,使用这个api可以节约网络资源。例如当用户离开一个应用时。不管同步还是异步的call都可以取消。也可以通过tags来同时取消多个请求。当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call,在后续断点续下是这个方法暂停下载就是通过这个方法来实现的。
OkHttpClient中的Call newCall(Request request)
就会返回一个Call实例,如果要需要本次请求就可以调用cancel()方法即可。
信任所有的https请求
我们知道OkHttp是支持https请求的,事实上这里所说的支持都是CA机构颁发的证书,默认情况下是可以信任的。然而对于自签名的网站,什么叫自签名呢?就是自己通过keytool去生成一个证书,然后使用,并不是CA机构去颁发的。使用自签名证书的网站,大家在使用浏览器访问的时候,一般都是报风险警告,好在有个大名鼎鼎的网站就是这么干的,https://kyfw.12306.cn/otn/,点击进入12306的购票页面就能看到了。如果想了解更多的https相关内容可以点这篇博客Android Https相关完全解析 当OkHttp遇到Https。
下图是我们直接使用OkHttp访问该网址抛出的异常:
下面是如何添加对所有的https网站的信任:
private static class MyHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { return true; } } private static class MyTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } } OkHttpClient.Builder builder = new OkHttpClient.Builder(); // ignore HTTPS Authentication builder.hostnameVerifier(new MyHostnameVerifier()); try { SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, new TrustManager[] { new MyTrustManager() }, new SecureRandom()); builder.sslSocketFactory(sc.getSocketFactory()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); }
Dispatcher
OkHttp总一个OkHttpcClient实例可以同时支持5个线程并发的,在上传文件是有时候我们希望可以单个文件形式上传,在这种情况下就需要用到这个类了。
Dispatcher dispatcher = httpClient.dispatcher(); dispatcher.setMaxRequestsPerHost(3); clientBuilder.dispatcher(dispatcher);
上面代码就是设置了最大可以同时运行三个请求。前面所说的取消特定tag的请求Call,通过Dispatcher的实现如下:
public void cancel(Object tag) { Dispatcher dispatcher = getInstance().dispatcher(); for (Call call : dispatcher.queuedCalls()) { if (tag.equals(call.request().tag())) { call.cancel(); } } for (Call call : dispatcher.runningCalls()) { if (tag.equals(call.request().tag())) { call.cancel(); } } }
文件的上传下载
在这一篇博文中没有涉及到断点续下的知识点,这里讲的文件上传就是模拟的在网页中表单上传文件的方式。文件下载核心我们如何重写ResponseBody,文件上传就是重写RequestBody,在这边文章中就不做过多的介绍了,在下一篇中准备详细讲一下,包括上传和断点续下,OkHttp官方的示例中已经有上传文档的demo代码了,问题就是我们如何将上传的进度回调放在UI线程中,这一点在上一篇文章中已经介绍过了,就是通过Handler,在Handler中传入一个Looper,通过Handler机制实现可以在主线程更新UI。可以参考github上这两个库OkHttp upload and download progress support和 OkHttp封装,支持GET、POST、UI线程回调、JSON格式解析、链式调用、小文件上传下载及进度监听等功能。