OKHttp
一:是什么(What)
大家都知道HTTP是现代应用网络的方式。在数据和媒体中进行交换。有效地进行HTTP使您的东西加载更快,并节省带宽。那么OkHttp就是默认情况下高效的HTTP客户端。
二:为什么(Why)
1:缓存响应数据来减少重复的网络请求
2:可以从很多常用的连接问题中自动恢复;
Okhttp处理了很多的网络疑难杂症:会从很多 常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败时,OkHttp会尝试连接下一个IP。
3:使用起来非常的简单,可扩展性非常的强;
三:用来干什么
1:支持一般的get请求,post请求。
2:基于Http的文件上传,文件下载,上传下载的进度回调,
3:加载图片
4:支持请求回调,直接返回对象,
5: 表单请求
四:请求流程
1. 生成一个OkHttpClient(用以总的控制)
2. 用各种键值对包装我们的Request
3. 将请求加入队列生成一个Call管理请求
4. 若是同步请求直接执行excute等待处理返回Response,若为异步则实现Callback回调,在onResponse里获取Response参数
真正的请求是从创建Call对象开始的:如图
同步请求返回对象 call
异步请求返回对象 AsyncCall
每个 call对象只能被执行一次,如果想要一个完全一样的 call,可以利用 call.clone() 方法进行克隆。
重点:
异步请求是需要创建线程池的,通过创建线程来进行请求;
分析一下它的源码:
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
这里我们着重分析下Dispatcher,首先我们需要了解下线程池,以及反向代理模式
相比我们对于异步任务的需求应该遇到了不少,首先想到的便是Thread,handler等异步机制,Java已经做了很好的封装,但是当我们需要使用许多异步任务,而这些任务只做了一点点事情就完成了它的使命,当我们不断的创建,销毁线程的时候,对系统的开销是相当大的,因为这些过程也是耗费时间的,这个时候我们就需要用到线程池了,我们只要往池子里放任务,他就会自动帮你管理线程的创建与销毁,利用缓存复用减少线程销毁创建,优化系统性能。以下是线程池的优点:
1. 通过对线程进行缓存,减少了创建销毁的时间损失
2. 通过控制线程数量阀值,减少了当线程过少时带来的CPU闲置(比如说长时间卡在I\O上了)与线程过多时对JVM的内存与线程切换压力
而Disatcher内部的核心即线程池
public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }
· int corePoolSize: 最小并发线程数,这里并发同时包括空闲与活动的线程,如果是0的话,空闲一段时间后所有线程将全部被销毁。
· int maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理
· long keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间,类似于HTTP中的Keep-alive
· TimeUnit unit: 时间单位,一般用秒
· BlockingQueue workQueue: 工作队列
· ThreadFactory threadFactory: 单个线程的工厂,可以打Log,设置Daemon(即当JVM退出时,线程自动结束)等
为了解决单生产者多消费者问题,OkHttp采用了反向代理模式,来解决非阻塞,高并发问题
OkHttp工作模式
Dispatch模式
· int corePoolSize: 最小并发线程数,这里并发同时包括空闲与活动的线程,如果是0的话,空闲一段时间后所有线程将全部被销毁。
· int maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理
· long keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间,类似于HTTP中的Keep-alive
· TimeUnit unit: 时间单位,一般用秒
· BlockingQueue workQueue: 工作队列
· ThreadFactory threadFactory: 单个线程的工厂,可以打Log,设置Daemon(即当JVM退出时,线程自动结束)等
OkHttp内Dispatcher原理
其实当我们调用client.newCall(request).enqueue(newcallback(){...})的时候实质是下图
synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
此时判断线程池内有无空闲,否则进入等待队列,AsyncCall实质是Runnable,当任务执行完毕后,总会调用finally{
client.dispatcher().finished(this);}清除此任务。
我们回头看看OkhttpClient初始化内其它一些参数Expires,Cache-Control,Last-Modified,If-Modified-Since,ETag,If-None-Match...这些都牵扯到缓存问题。
Response response = getResponseWithInterceptorChain();
其实这个方法是返回我们的response对象。到这里整个流程已经完毕。
使用OkHttpClient 的时候需要注意以下几点:
1.最好只使用一个共享的OkHttpClient 实例,将所有的网络请求都通过这个实例处理。因为每个OkHttpClient实例都有自己的连接池和线程池,重用这个实例能降低延时,减少内存消耗,而重复创建新实例则会浪费资源。
2.OkHttpClient的线程池和连接池在空闲的时候会自动释放,所以一般情况下不需要手动关闭,但是如果出现极端内存不足的情况,可以使用以下代码释放内存:
client.dispatcher().executorService().shutdown();
//清除并关闭线程池
client.connectionPool().evictAll();
//清除并关闭连接池
client.cache().close();
3.如果对一些请求需要特殊定制,可以使用
OkHttpClient eagerClient = client.newBuilder()
.readTimeout(
500, TimeUnit.MILLISECONDS)
.build();
1. 这样创建的实例与原实例共享线程池、连接池和其他设置项,只需进行少量配置就可以实现特殊需求。
Request是网络请求的对象,其本身的构造函数是private的,只能通过Request.Builder来构建。下面代码展示了常用的设置项。
Request request =
newRequest.Builder()
.url(
"https://api.github.com/repos/square/okhttp/issues")
//设置访问url
.get()
//类似的有post、delete、patch、head、put等方法,对应不同的网络请求方法
.header(
"User-Agent",
"OkHttp Headers.java")
//设置header
.addHeader(
"Accept",
"application/json; q=0.5")
//添加header
.removeHeader(
"User-Agent")
//移除header
.headers(
newHeaders.Builder().add(
"User-Agent",
"OkHttp Headers.java").build())
//移除原有所有header,并设置新header
.addHeader(
"Accept",
"application/vnd.github.v3+json")
.build();
//构建request
在Request中使用post、patch等方法时,需要传入一个RequestBody参数,除了上一节讲到的构造方法外,RequestBody还有两个子类:FormBody和MultipartBody。
FromBody用于提交表单键值对,其作用类似于HTML中的