Okhttp

OkHttp简介

优点:

  1. 支持Http1、Http2、Quic以及WebSocket
  2. 连接池复用底层TCP(Socket),减少请求延时
  3. 无缝的支持GZIP减少数据流量
  4. 缓存响应数据减少重复的网络请求
  5. 请求失败自动重试主机的其他ip,自动重定向

设计模式:建造者,门面,责任链

**分发器:**内部维护队列与线程池,完成请求调配;

**拦截器:**五大默认拦截器完成整个请求过程。

OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
        .url(url)
        .get()//默认就是GET请求,可以不写
        .build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d(TAG, "onFailure: ");
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d(TAG, "onResponse: " + response.body().string());
    }
});

分发器

异步流程
//OkhttpClient.java
//newRealCall方法new了一个RealCall返回;
@Override 
public Call newCall(Request request) {  
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

//RealCall.java
@Override public void enqueue(Callback responseCallback) {
    //enqueue不能被执行两次
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
  ...
      //用AsyncCall封装RealCall,并且调用dispatcher的enqueue,传入AsyncCall
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

//Dispatcher.java
//异步请求同时存在的最大请求
private int maxRequests = 64;
//异步请求同一域名同时存在的最大请求
private int maxRequestsPerHost = 5;
//闲置任务(没有请求时可执行一些任务,由使用者设置)
private @Nullable Runnable idleCallback;
//异步请求使用的线程池
private @Nullable ExecutorService executorService;
//异步请求等待执行队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//异步请求正在执行队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//同步请求正在执行队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();


synchronized void enqueue(AsyncCall call) {
     //当正在执行的任务未超过最大限制64,并且同一Host的请求不超过5个
	if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) <maxRequestsPerHost) {
         //添加到正在执行队列,
		runningAsyncCalls.add(call);
        //提交给线程池。NamedRunnable的run方法中调用AsyncCall的execute方法;
		executorService().execute(call);
	} else {//否则先加入等待队列。
		readyAsyncCalls.add(call);
	}
}

//异步请求调用传入ture
void finished(AsyncCall call) {
	finished(runningAsyncCalls, call, true);
}
//同步请求调用传入false
void finished(RealCall call) {
	finished(runningSyncCalls, call, false);
}

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
	int runningCallsCount;
	Runnable idleCallback;
	synchronized (this) {
		//不管异步还是同步,执行完后都要从队列移除
		if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
        //只有异步才会执行promoteCalls方法
		if (promoteCalls) promoteCalls();
		runningCallsCount = runningCallsCount();
		idleCallback = this.idleCallback;
	}
	// 没有任务执行执行闲置任务
	if (runningCallsCount == 0 && idleCallback != null) {
		idleCallback.run();
	}
}

private void promoteCalls() {
    //正在执行队列满了返回;没有等待任务返回
    if (runningAsyncCalls.size() >= maxRequests) return; 
    if (readyAsyncCalls.isEmpty()) return;
	//遍历等待执行队列,
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();
	  //如果满足主机数不超5,就取出任务放入执行队列。放入线程池执行
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; 
    }
  }

//AsyncCall.java
@Override
protected void execute() {
	boolean signalledCallback = false;
	try {
        //这个方法是OkHttp的核心:拦截器责任链。
		Response response = getResponseWithInterceptorChain();
		//.......
	} catch (IOException e) {
		//......
	} finally {
		//请求完成,finally中调用dispatcher的finish方法,最终调用三个参数的
		client.dispatcher().finished(this);
	}
}

同步流程
//RealCall.jaav
@Override public Response execute() throws IOException {
  ...
    try {
       //就是将任务放入同步执行队列
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
        //传入的是RealCall所以最后调用传入的false
      client.dispatcher().finished(this);
    }
  }
线程池
//Dispather.java
public synchronized ExecutorService executorService() {
	if (executorService == null) {
		executorService = new ThreadPoolExecutor(
			0, //核心线程
			Integer.MAX_VALUE, //最大线程
			60, TimeUnit.SECONDS, 
			new SynchronousQueue<Runnable>(), //线程等待队列
			Util.threadFactory("OkHttp Dispatcher", false));
	}
	return executorService;
}

OkHttp的分发器中的线程池定义如上,能够得到最大的吞吐量。即当需要线程池执行任务 时,如果不存在空闲线程不需要等待,马上新建线程执行任务!

假设向线程池提交任务时,核心线程都被占用的情况下:

ArrayBlockingQueue :基于数组的阻塞队列,初始化需要指定固定大小。 当使用此队列时,向线程池提交任务,会首先加入到等待队列中,当等待队列满了之后,再次提交任务,尝试加入 队列就会失败,这时就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。所以 最终可能出现后提交的任务先执行,而先提交的任务一直在等待。

LinkedBlockingQueue :基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定。 当指定大小后,行为就和 ArrayBlockingQueu 一致。而如果未指定大小,则会使用默认的 Integer.MAX_VALUE 作 为队列大小。这时候就会出现线程池的最大线程数参数无用,因为无论如何,向线程池提交任务加入等待队列都会 成功。最终意味着所有任务都是在核心线程执行。如果核心线程一直被占,那就一直等待。

SynchronousQueue : 无容量的队列。 使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败。而失败后 如果没有空闲的非核心线程,就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任 务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合 Integer.MAX_VALUE 就实现了真正的 无等待。

但是需要注意的时,我们都知道,进程的内存是存在限制的,而每一个线程都需要分配一定的内存。所以线程并不 能无限个数。那么当设置最大线程数为 Integer.MAX_VALUE 时,OkHttp同时还有最大请求任务执行个数: 64的限 制。这样即解决了这个问题同时也能获得最大吞吐。

拦截器

//RealCall.java
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

//Interceptor拦截器顶层接口,内部类Chain,链条是责任链串联起来的关键。调用proceed方法开启责任链,再proceed方法中会再次创建Chain,构造方法传入interceptors集合,index;控制index+1;可以调到下一个拦截器。通过index获取拦截器调用拦截器的intercept将新建的chain传入,此时第一个拦截器产生作用。在intercept方法再次调用proceed方法
public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();
    Response proceed(Request request) throws IOException;
	...	
  }
}

默认情况下有五大拦截器 :

  1. RetryAndFollowUpInterceptor :第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求
  2. BridgeInterceptor 补全请求,并对响应进行额外处理
  3. CacheInterceptor 请求前查询缓存,获得响应并判断是否需要缓存
  4. ConnectInterceptor 与服务器完成TCP连接
  5. CallServerInterceptor 与服务器通信;封装请求数据与解析响应数据(如:HTTP报文)
一、重试及重定向拦截器

本拦截器是整个责任链中的第一个,这意味着它会是首次接触到 Request 与最后接收到 Response 的角色,在这个 拦截器中主要功能就是判断是否需要重试与重定向。 重试的前提是出现了 RouteException 或者 IOException 。一但在后续的拦截器执行过程中出现这两个异常,就会 通过 recover 方法进行判断是否进行连接重试。 重定向发生在重试的判定之后,如果不满足重试的条件,还需要进一步调用 followUpRequest 根据 Response 的响 应码(当然,如果直接请求失败, Response 都不存在就会抛出异常)。 followup 最大发生20次。

二、桥接拦截器

桥接拦截器的执行逻辑主要就是以下几点 对用户构建的 Request 进行添加或者删除相关头部信息,以转化成能够真正进行网络请求的 Request 将符合网络 请求规范的Request交给下一个拦截器处理,并获取 Response 如果响应体经过了GZIP压缩,那就需要解压,再构 建成用户可用的 Response 并返回

在补全了请求头后交给下一个拦截器处理,得到响应后,主要干两件事情: 1、保存cookie,在下次请求则会读取对应的数据设置进入请求头,默认的 CookieJar 不提供实现 2、如果使用gzip返回的数据,则使用 GzipSource 包装便于解析。

三、缓存拦截器

必须调用OkHttpClient cache方法;CacheInterceptor ,在发出请求前,判断是否命中缓存。如果命中则可以不请求,直接使用缓存的响应。 (只会存 在Get请求的缓存)

步骤为:

1、从缓存中获得对应请求的响应缓存

2、创建 CacheStrategy ,创建时会判断是否能够使用缓存,在 CacheStrategy 中存在两个成员: networkRequest 与 cacheResponse 。他们的组合如下:

networkRequestcacheResponse说明
NullNot Null直接使用缓存
Not NullNull向服务器发起请求
NullNullgg,okhttp直接返回504
Not NullNot Null发起请求,若得到响应为304(无修改),则更新缓存响应并返回

3、交给下一个责任链继续处理

4、后续工作,返回304则用缓存的响应;否则使用网络响应并缓存本次响应(只缓存Get请求的响应)

四、链接拦截器

首先我们看到的 StreamAllocation 这个对象是在第一个拦截器:重定向拦截器创建的,但是真正使用的地方却在 这里。

“当一个请求发出,需要建立连接,连接建立后需要使用流用来读写数据”;而这个StreamAllocation就是协调请 求、连接与数据流三者之间的关系,它负责为一次请求寻找连接,然后获得流来实现网络通信。

这里使用的 newStream 方法实际上就是去查找或者建立一个与请求主机有效的连接,返回的 HttpCodec 中包含了 输入输出流,并且封装了对HTTP请求报文的编码与解码,直接使用它就能够与请求主机完成HTTP通信。 StreamAllocation 中简单来说就是维护连接: RealConnection ——封装了Socket与一个Socket连接池。可复用 的 RealConnection 需要:

1、连接到达最大并发流或者连接不允许建立新的流;如http1.x正在使用的连接不能给其他人用(最大并发流为:1)或者 连接被关闭;那就不允许复用;

2、 DNS、代理、SSL证书、服务器域名、端口完全相同则可复用; 如果上述条件都不满足,在HTTP/2的某些场景下可能仍可以复用(http2先不管)。 所以综上,如果在连接池中找到个连接参数一致并且未被关闭没被占用的连接,则可以复用。

五、请求服务器拦截器

在这个拦截器中就是完成HTTP协议报文的封装与解析

一般出现于上传大容量请求体或者需要验证。代表了先询问服务器是否原因接 收发送请求体数据,

OkHttp的做法: 如果服务器允许则返回100,客户端继续发送请求体; 如果服务器不允许则直接返回给用户。

同时服务器也可能会忽略此请求头,一直无法读取应答,此时抛出超时异常

补充:

代理
加载安全证书
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值