Okhttp

简介

Http是现在应用常用的一种交换数据和媒体的网络方式,高效地使用http能让资源加载更快,节省带宽。Okhttp是一个高效的Http客户端,它的特性如下:

  • 支持Http/2,允许所有同一个主机地址的请求共享同一个socket连接
  • 连接池能够减少请求延时
  • 透明的GZIP压缩能够减少响应数据的大小
  • 缓存响应内容,避免一些完全重复的请求

当网络请求出现问题的时候okhttp依然坚守自己的职责,它会自动恢复一般的连接问题,如果你的服务有多个ip地址,当第一个ip请求失败时,Okhttp会交替尝试你配置的其他Ip,okhttp使用现代TLS技术初始化新的连接,当握手失败时会回退到TLS1.0

使用

异步GET请求

String url = "http://www.baidu.com";
		OkHttpClient okHttpClient = new OkHttpClient();
		final Request request = new Request.Builder()
				.url(url)
				.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());
			}
		});

异步发起的请求会被加入到Dispatcher中的runningAsyncCalls双端队列中通过线程池来执行

同步GET请求

它的最后一步是通过execute()来提交请求,这种方式会阻塞调用线程,所以在Android中应放在子线程中执行,否则可能引起ANR异常

String url = "http://www.baidu.com";
		OkHttpClient okHttpClient = new OkHttpClient();
		final Request request = new Request.Builder()
				.url(url)
				.get()
				.build();
		final Call call = okHttpClient.newCall(request);

		new Thread(new Runnable()
		{
			@Override
			public void run()
			{
				try
				{
					Response response = call.execute();
					Log.d(TAG,"run: "+response.body().string());
				}catch (IOException e){
					e.printStackTrace();
				}
			}
		}).start();

POST方式提交String

这种方式与前面的区别就是在构造Request对象时,需要多构造一个RequestBody对象,用它来携带我们要提交的数据。在构造RequestBody时需要指定MediaType,用于描述请求/响应body的内容类型

MediaType mediaType = MediaType.parse("text/x-markdown;charset=utf-8");
		String requestBody = "I'm Jdqm";
		Request request = new Request.Builder()
				.url("https://api.github.com/markdown/raw")
				.post(RequestBody.create(mediaType,requestBody))
				.build();
		OkHttpClient okHttpClient = new OkHttpClient();
		okHttpClient.newCall(request).enqueue(new Callback()
		{
			@Override
			public void onFailure(Call call, IOException e)
			{
				Log.d(TAG,"onFailure:" + e.getMessage());
			}

			@Override
			public void onResponse(Call call, Response response) throws IOException
			{
               Log.d(TAG,response.protocol()+" "+response.code() + " " + response.message());
				Headers headers = response.headers();
				for (int i=0;i<headers.size();i++){
					Log.d(TAG,headers.name(i)+":"+headers.value(i));
				}
				Log.d(TAG,"onResponse: "+response.body().toString());
			}
		});

拦截器-interceptor

用户可以传入的interceptor分为两类:

  • 一类是全局的interceptor,该类interceptor在整个拦截器链中最早被调用,通过OkHttpClient.Builder().addInterceptor(Interceptor)传入

  • 另外一类是非网页请求的interceptor,这类拦截器只会在非网页请求中被调用,并且是在组装完请求之后,真正发起网络请求前被调用,所有的interceptor被保存在Listinterceptor集合中,按照添加顺序来逐个调用
    在这里插入图片描述

其他

  • 推荐让OkHttpClient保持单例,用同一个OkHttpClient实例来执行你的所有请求,因为每一个OkHttpClient实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个OkHttpClient实例,显然就是一种资源的浪费
  • 每一个Call只能执行一次,否则会报异常

源码分析

在Okhttp中,其灵活性很大程度上体现在可以intercept任意一个环节,而这个优势便是okhttp整个请求响应架构体系的精髓所在
在这里插入图片描述

在Okhttp内部使用构造器模式初始化了一些配置信息:支持协议、任务分发器(其内部包含一个线程池,执行异步请求)、连接池(其内部包含一个线程池,维护connection)、连接/读/写超时时长等信息

public Builder() {
    dispatcher = new Dispatcher(); //任务调度器
    protocols = DEFAULT_PROTOCOLS; //支持的协议
    connectionSpecs = DEFAULT_CONNECTION_SPECS;
    eventListenerFactory = EventListener.factory(EventListener.NONE);
    proxySelector = ProxySelector.getDefault();
    cookieJar = CookieJar.NO_COOKIES;
    socketFactory = SocketFactory.getDefault();
    hostnameVerifier = OkHostnameVerifier.INSTANCE;
    certificatePinner = CertificatePinner.DEFAULT;
    proxyAuthenticator = Authenticator.NONE;
    authenticator = Authenticator.NONE;
    connectionPool = new ConnectionPool(); //连接池
    dns = Dns.SYSTEM;
    followSslRedirects = true;
    followRedirects = true;
    retryOnConnectionFailure = true;
    connectTimeout = 10_000;//超时时间
    readTimeout = 10_000;
    writeTimeout = 10_000;
    pingInterval = 0;
}

通过new Dispatcher()创建了一个任务调度器:Dispatcher,它定义了三个双向任务队列,其中有两个是异步队列:准备执行的请求队列readyAsyncCalls、正在执行的请求队列runningAsyncCalls;一个正在运行的同步请求队列runningSyncCalls

public final class Dispatcher {
    private int maxRequests = 64; //最大请求数量
    private int maxRequestsPerHost = 5; //每台主机最大的请求数量
    private @Nullable Runnable idleCallback;
    
    /** Executes calls. Created lazily. */
    private @Nullable ExecutorService executorService; //线程池
    
    /** Ready async calls in the order they'll be run. */
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
    
    /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
    
    /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
    private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
    
    /** 这个线程池没有核心线程,线程数量没有限制,空闲60s就会回收*/
    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;
    }
}  

另外还有一个线程池executorService,这个线程池跟Android中的CachedThreadPoll非常类似,适用于大量的耗时较短的异步任务

接着会通过OkHttpclient和Request构造一个Call对象,它的实现是RealCall

public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket){
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}

在RealCall的构造方法中创建了一个RetryAndFollowUpInterceptor,用于处理请求错误和重定向等,默认情况下它也是第一个拦截器,除非调用了OkHttpClient.Builder.addInterceptor(Interceptor)来添加全局的拦截器。

RealCall.enqueue(Callback)

public void enqueue(Callback responseCallback) {
    synchronized (this) {
        //每个请求只能之执行一次
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

一个Call只能执行一次,否则会抛出异常,这里创建了一个AsyncCall并将Callback传入,接着再交给任务分发器Dispatcher来进一步处理

synchronized void enqueue(AsyncCall call) {
    //正在执行的任务数量小于最大值(64),并且此任务所属主机的正在执行任务小于最大值(5)
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        runningAsyncCalls.add(call);
        executorService().execute(call);
    } else {
        readyAsyncCalls.add(call);
    }
}

从enqueue()中可以发现对请求的入队做了一些限制,若正在执行的请求数量小于最大值(默认64),并且此请求所属主机的正在执行任务小于最大值(默认5),就加入正在运行的队列并通过线程池来执行任务,否则加入准备执行队列中

AsyncCall

AsyncCall继承自NamedRunnable,而NamedRunnable实现了Runnable接口,他的作用有两个:

  • 采用模板方法的设计模式,让子类将具体的操作放在execute()方法中
  • 给线程指定一个名字,方便监控线程的活动状态

NamedRunnable

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      //采用模板方法让子类将具体的操作放到此execute()方法
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

AsyncCall

final class AsyncCall extends NamedRunnable {
    //省略...
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //调用 getResponseWithInterceptorChain()获得响应内容
        Response response = getResponseWithInterceptorChain(); //①
        if (retryAndFollowUpInterceptor.isCanceled()) {
          //这个标记为主要是避免异常时2次回调
          signalledCallback = true;
          //回调Callback告知失败
          responseCallback.onFailure(RealCall.this, new IOException("Canceled")); 
        } else {
          signalledCallback = true;
          //回调Callback,将响应内容传回去
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        //不管请求成功与否,都进行finished()操作
        client.dispatcher().finished(this);//②
      }
    }
}

client.dispatcher().finish(this)

void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
}

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!");
        if (promoteCalls) promoteCalls(); //推动下一个任务的执行
        runningCallsCount = runningCallsCount();//同步+异步的正在执行任务数量
        idleCallback = this.idleCallback;
    }
    //如果没有正在执行的任务,且idleCallback不为null,则回调通知空闲了
    if (runningCallsCount == 0 && idleCallback != null) {
        idleCallback.run();
    }
}

其中,promoteCalls()推动下一个任务执行,在条件满足的情况下, 将readyAsyncCalls中的任务移动到runningAsyncCalls中。并交给线程池来执行。

getResponseWithInterceptorChain()

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>(); //这是一个List,是有序的
    interceptors.addAll(client.interceptors());//首先添加的是用户添加的全局拦截器
    interceptors.add(retryAndFollowUpInterceptor); //错误、重定向拦截器
   //桥接拦截器,桥接应用层与网络层,添加必要的头、
    interceptors.add(new BridgeInterceptor(client.cookieJar())); 
    //缓存处理,Last-Modified、ETag、DiskLruCache等
    interceptors.add(new CacheInterceptor(client.internalCache())); 
    //连接拦截器
    interceptors.add(new ConnectInterceptor(client));
    //从这就知道,通过okHttpClient.Builder#addNetworkInterceptor()传进来的拦截器只对非网页的请求生效
    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会将前面的client.interceptors()全部加入其中,还有在创建的RealCall时的retryAndFollowUpInterceptor加入其中,接着还创建并添加了BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor,最后通过RealInterceptorChain.proceed(Request)来执行整个interceptor chain

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值