盘点常用的Android开发库(6) -- OkHttp

一、简介

关于okhttp相信不做介绍,大家也都知道是干嘛的,因为它真的很常见。这里就稍作介绍,OkHttp是基于Http协议的网络请求框架,也是android端最火热的轻量级框架之一。

它的主要优势是:

  • 允许连接到同一个主机地址的所有请求,提高请求效率
  • 共享Socket,减少对服务器的请求次数
  • 通过连接池,减少了请求延迟
  • 缓存响应数据来减少重复的网络请求
  • 减少了对数据流量的消耗
  • 自动处理GZip压缩

说白了就是可以更加有效快速的通过Http请求获取数据,并且能够节省流量减小带宽。

 

二、使用

2.1 依赖注入

compile 'com.squareup.okhttp3:okhttp:3.10.0'

我这里的OkHttp3不是最新的版本,最新的可以在github上Okhttp官网查看。

 

2.2 如何使用

第一步,创建一个okHttpClient实例。

OkHttpClient okHttpClient = new OkHttpClient();

第二步,通过Builder获取一个Request,这一步需要传入请求的url和请求方式,如果是Post请求还需要在之前定义RequestBody,并传入到Post方法中。

    //Get请求
    Request request = new Request.Builder()
        .url(url)
        .get()
        .build();


    //Post请求
    MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
    String requestBody = "I am Jdqm.";
    Request request = new Request.Builder()
        .url(url)
        .post(RequestBody.create(mediaType, requestBody))
        .build();

第三步,通过第一步创建okHttpClient的newCall方法获取一个Call,并通过Call中的enqueue()方同步请求法(异步请求)或者execute()(同步请求)获取Response响应体。

        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 {
                String body = response.body().string();
                Log.d(TAG, "onResponse: "+body);
            }
        });

最后我们只需要通过解析处理Response即可得到想要的数据,至此一个完整的OkHttp请求结束。当然OKHttp还有一些其他的用法,这里就不再一一赘述了,基本用法就是这些。

 

2.3 拦截器

拦截器是OkHttp的核心部分,在OkHttp最核心的请求方法中就是通过一组拦截器和责任链来实现完整的请求的,其中拦截器集合中第一个拦截器就是用户自定义的拦截器,后面的原理分析会讲到,这里我们来说说如何给你的请求追加自定义的拦截器。

第一步,创建一个MyIntercepter来实现OkHttp的Intercepter接口。并在其中实现你想做的事情。

/**
 * 自定义的拦截器
 */

public class MyIntercepter implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        //获取责任链中的请求对象
        Request request = chain.request();

        //TODO 这里可以做一些事情 比如拦截特定的Url,打印请求时间等

        //通过责任链请求获取响应体
        Response response = chain.proceed(request);
        return response;
    }
}

第二步,在创建OkHttp实例的时候通过Builder来构造,并在其中添加自定义的拦截器。

        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new MyIntercepter())
                .build();

后面的步骤就一样啦。

 

三、原理分析

接下来我们来分析分析OkHttp的原理,我们跟着前面的提到的使用步骤走。首先我们来看一下OkHttpClient的构造函数。

  public OkHttpClient() {
    this(new Builder());
  }

  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

    ... ...
  }

可以看到OkHttpClient构造函数中其实自动使用了Builder来构造OkHttpClient实例,不需要用户自己构造,当然如果我们需要添加拦截器或者手动配置一些东西,则需要手动builder。

下面我们看下Builder中有哪些重要的属性。

public static final class Builder {
    Dispatcher dispatcher;      //调度器  用以对异步请求进行调度策略
    @Nullable Proxy proxy;      //代理    封装了http和socket
    List<Protocol> protocols;   //Http通信协议的集合,我们常用的是http/1.1协议
    List<ConnectionSpec> connectionSpecs; //连接配置的集合
    final List<Interceptor> interceptors = new ArrayList<>(); //拦截器的集合,这个很重要,自定义的拦截器也是添加在这个集合里的
    final List<Interceptor> networkInterceptors = new ArrayList<>(); //网络拦截器的集合,同上
    EventListener.Factory eventListenerFactory; //事件监听器  我们可以通过扩展此类,来监听应用程序调用Http请求的数量,大小和持续时间
    ProxySelector proxySelector;   //代理选择器 选择连接到服务器时的代理服务器
    CookieJar cookieJar;        //提供HTTP cookie的策略和持久性。
    @Nullable Cache cache;      //缓存
    @Nullable InternalCache internalCache;    //内部缓存
    SocketFactory socketFactory;   //socket工厂
    @Nullable SSLSocketFactory sslSocketFactory;    //ssl socket工厂
    @Nullable CertificateChainCleaner certificateChainCleaner;  //从Java的内置TLS API返回的原始数组中计算有效证书链
    HostnameVerifier hostnameVerifier;  //用以验证主机名的基本接口
    CertificatePinner certificatePinner;  //证书相关
    Authenticator proxyAuthenticator;  //响应来自远程Web服务器或代理服务器的身份验证质询
    Authenticator authenticator;
    ConnectionPool connectionPool;   //连接池  很重要  是OkHttp策略之一
    Dns dns;
    boolean followSslRedirects;   //是否遵循SSL重定向
    boolean followRedirects;      //是否遵循重定向
    boolean retryOnConnectionFailure;    //是否失败重连
    int connectTimeout;        //连接超时时长
    int readTimeout;
    int writeTimeout;
    int pingInterval;

    ... ...

}

属性有点儿多,了解下就行其中最重要的就是拦截器和Dispatcher。

接下来我们看看是如何创建Request的,首先还是看下builder中的几个属性,没什么好说的。这里创建Request就是通过建造者模式来配置并创建的。

public static class Builder {
    HttpUrl url;               //url
    String method;             //请求方式
    Headers.Builder headers;   //请求头
    RequestBody body;          //请求体
    Object tag;

    ... ...

}

接下来的过程则是通过OkHttpClient的newCall方法来获取Call。首先我们来看下newCall方法。

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

可以看到这里是直接调用RealCall的newRealCall方法。而这个RealCall则是最终获取到的Call实例。

final class RealCall implements Call {
  final OkHttpClient client;
  final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;

  ... ...
    
  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;
  }  

  ... ...

}

RealCall实现了Call接口,并且实现了execute()、enqueue()等方法。其中enqueue()是异步请求,execute()是同步请求。我们接下来继续跟进enqueue()方法。

  @Override 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));
  }

这里利用同步锁避免重复请求,然后调用eventListener.callStart记录这次请求的开始状态、时间等,最后在通过前面提到的调度器dispatcher的enqueue()方法进行调度,继续追踪。

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

 这里就是对任务队列做个判断,判断正在执行的请求是否小于最大请求数,同主机地址正在执行请求数是否小于最大同主机地址请求数。如果同时满足,则将这个请求加入队列中,并立刻执行,否则就只加入队列中,等待调度器调度。

  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;
  }

最后通过线程池来执行请求任务,最终执行AsyncCall中的execute()开启线程。

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        ... ...
      } catch (IOException e) {
        ... ...
      } finally {
        client.dispatcher().finished(this);
      }
    }

这里OkHttp的核心部分来了,执行getResponseWithInterceptorChain()来获取我们所需要的Response。继续追踪该方法。

  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);
  }

这里首先创建了一个拦截器集合,并加入了以上几个拦截器,之前我们也提到了,用户自定义的拦截器也是在其中的。最后创建一个责任链RealIntercepterChain,并把拦截器的集合传入其中,最后调用了chain.proceed()方法。我们看下proceed方法做了些什么。Intercepter接口中Chain接口的抽象方法,因此我们需要结合RealInterceptorChain来看。

在RealInterceptorChain中的proceed方法是这样的(简化后的)。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    
    ......

    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    ......

    return response;
  }

这里就是创建一个新的责任链,并且传入的下标+1,然后获取当前下标的拦截器,并执行拦截器的intercept方法获取Response。这里我们需要结合实际的拦截器来看看intercept方法,我们以BridgeInterceptor为例。

public final class BridgeInterceptor implements Interceptor {
  private final CookieJar cookieJar;

  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    
    ......
    //这里省略的部分是BridgeIntercepter具体的功能,也就是将我们的请求转换成服务器能看懂的网络请求。

    Response networkResponse = chain.proceed(requestBuilder.build());

    ......

    return responseBuilder.build();
  }

  ......

}

可以看到在intercept方法就是拦截器的具体功能的实现部分,这里被我省略了,有兴趣的可以自行查看源代码。在这里最关键的一句代码就是调用chain.proceed方法。并传入了该拦截器已经处理好的请求体。接下来就是不断的递归过程,直到处理完最后一个拦截器。最终可以通过proceed方法拿到我们所需要的Response。

以上就是OkHttp请求的核心思想,至此原理也分析的差不多了。如果对几个拦截器中具体实现敢兴趣的可以自己看下源码,也便于更好地理解OkHttp。

发布了17 篇原创文章 · 获赞 6 · 访问量 4346

猜你喜欢

转载自blog.csdn.net/ledding/article/details/105114020