OKHttp介绍

OkHttp是一个高效的HTTP库:

参考:http://blog.csdn.net/ustcrding/article/details/53185801

  1. 会从很多常用的连接问题中自动恢复。
  2. 如果服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。
  3. OkHttp还处理了代理服务器问题和SSL握手失败问题。
  4. OKHttp支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求如果SPDY不可用,则通过连接池来减少请求延迟。

总体设计:

这里写图片描述

上 面是OKHttp总体设计图,主要是通过Diapatcher不断从RequestQueue中取出请求(Call),根据是否已缓存调用Cache或 Network这两类数据获取接口之一,从内存缓存或是服务器取得请求的数据。该引擎有同步和异步请求,同步请求通过Call.execute()直接返 回当前的Response,而异步请求会把当前的请求Call.enqueue添加(AsyncCall)到请求队列中,并通过回调(Callback) 的方式来获取最后结果。

关于OKHttp的请求流程图

这里写图片描述

代码使用:

在向网络发起请求的时候,我们最常用的就是GET和POST,下面就来看看如何使用 。
⑴ GET请求

String url = "https://www.baidu.com/";
        //创建OkHttpClient对象
        OkHttpClient okHttpClient = new OkHttpClient();
        //得到request对象
        Request request = new Request.Builder().url(url).build();
        //得到call对象
        Call call = okHttpClient.newCall(request);
        //同步网络请求
        try {
            Response response = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //异步网络请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("======fail====" + e.toString());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("======onResponse====" + response.body().string());
            }
        });

OkHttp支持同步和异步的方式来操作网络请求,如上面的execcute方法和enqueue方法所示。同步方法返回的response以及异步方法回调的参数是response,一般情况下,比如我们希望获得返回的字符串,可以通过response.body().string()获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputStream,则调用response.body().byteStream()。

在使用同步和异步方式来操作网络请求时,需要注意两点:

① 由于Android本身是不允许在UI线程做网络请求操作的,因此在利用同步方式来操作网络请求时,我们需要自己开启一个线程来进行操作。

② 异步方式操作网络请求的异步回调是在非UI线程中,因此如果有更新UI的操作记得用Handler或者其他方式。

⑵ POST请求

String url = "https://www.baidu.com/";
        //创建OkHttpClient对象
        OkHttpClient okHttpClient = new OkHttpClient();
        ///构建RequestBody对象,调用add()方法构建我们的键值对
        RequestBody requestBody = new FormBody.Builder().add("name", "value").build();
        //在构建Request对象时,调用post方法,传入RequestBody对象
        Request request = new Request.Builder().url(url).post(requestBody).build();
        //得到call对象
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("======fail====" + e.toString());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("======onResponse====" + response.body().string());
            }
        });

可以看出来,Post请求需要通过构建RequestBody对象来将post参数都是传到Request里面。Post请求因此最的调用方式也和GET方式一样,也支持同步和异步的网络请求方式。

⑶ 缓存设置
在网络请求中,缓存技术是一项应用比较广泛的技术,需要对请求过的网络资源进行缓存,而okhttp也支持这一技术,也使用十分方便,只需调用OkHttpClient类中的cache方法即可。

String url = "https://www.baidu.com/";
        //定义缓存大小
        int cacheSize = 30 * 1024 * 1024; // 10 MiB
        //创建缓存
        Cache cache = new Cache(getCacheDir(), cacheSize);
        OkHttpClient client = new OkHttpClient.Builder()
                .cache(cache)
                .build();
        Request request = new Request.Builder().url(url).build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });

但有时候即使在有缓存的情况下我们依然需要去后台请求最新的资源(比如资源更新了)这个时候可以使用强制走网络来要求必须请求网络数据。

Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

request =request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build();
Response response =client.newCall(request).execute();

同样的我们可以使用 FORCE_CACHE 强制只使用缓存的数据,但如果请求必须从网络获取才有数据,但又使用了FORCE_CACHE 策略就会返回504错误。

⑷ 取消请求

网络操作中,经常会使用到对请求的cancel操作,OkHttp的也提供了这方面的接口,call的cancel操作。使用Call.cancel()可以立即停止掉一个正在执行的call。如果一个线程正在写请求或者读响应,将会引发IOException,同时可以通过Request.Builder.tag(Object tag)给请求设置一个标签,并使用OkHttpClient.cancel(Objecttag)来取消所有带有这个tag的call。但如果该请求已经在做读写操作的时候,cancel是无法成功的,会抛出IOException异常。

请求流程

流程概述:

当我们用OkHttpClient.newCall(request)进行execute/enenqueue时,实际是将请求Call放到了Dispatcher中,OkHttp使用Dispatcher进行线程分发,它有两种方法,一个是普通的同步单线程;另一种是使用了队列进行并发任务的分发(Dispatch)与回调,我们下面主要分析第二种,也就是队列这种情况,这也是okhttp能够竞争过其它库的核心功能之一。

使用OkHttp3发送Http请求并获得响应的过程大体为:

1.创建OkHttpClient对象。OkHttpClient为网络请求执行的一个中心,它会管理连接池,缓存,SocketFactory,代理,各种超时时间,DNS,请求执行结果的分发等许多内容。

2.创建Request对象。Request用于描述一个HTTP请求,比如请求的方法是”GET”还是”POST”,请求的URL,请求的header,请求的body,请求的缓存策略等。

3.利用前面创建的OkHttpClient对象和Request对象创建Call对象。Call是一次HTTP请求的Task,它会执行网络请求以获得响应。OkHttp中的网络请求执行Call既可以同步进行,也可以异步进行。调用call.execute()将直接执行网络请求,阻塞直到获得响应。而调用call.enqueue()传入回调,则会将Call放入一个异步执行队列,由ExecutorService在后台执行。

4.执行网络请求并获取响应。

请求流程源码分析
从上述的请求流程中可以看出,OkHttpClient同步请求和异步请求调用的接口是不一样的,但它们最后都是殊途同归地走到使用拦截器链来进行网络请求。

我们从源码的角度来分析它是如何实现的,同步请求较简单,如下所示:

RealCall类中

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    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 {
      client.dispatcher().finished(this);
    }
  }

通过调用RealCall.execute()同步执行Http请求的过程大体为:

  1. 调用client.dispatcher().executed(this)向client的dispatcher注册当前Call。

  2. 调用getResponseWithInterceptorChain()执行网络请求并获得响应。

  3. 调用client.dispatcher().finished(this)向client的dispatcher注销当前Call。

异步请求比较复杂,Call对外提供的接口的实现代码如下所示:

RealCall类中

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

可以看出,和同步不同的是,同步传入enqueue方法的参数是Call,异步传入的是AsyncCall,这个是什么呢,这个是Call里面的一个内部类,而且是一个继承了Runnable的内部类,我们先来看看这个Dispatcher类中的enqueue是怎么操作的。

Dispatcher类中

synchronized void enqueue(AsyncCall call) {
    //判断当前运行的线程是否超过最大线程数(64),以及请求同一host是否要超过相同请求同时存在的最大数目
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      //将请求放到线程池里运行
      executorService().execute(call);
    } else {
    //不满足运行条件放到后备队列里
      readyAsyncCalls.add(call);
    }
  }

再来看看AsyncCall 的run里面的代码:

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          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 {
        client.dispatcher().finished(this);
      }
    }

通过调用RealCall.enqueue()异步执行Http请求的过程则为,创建AsyncCall并将之丢给client的dispatcher。而在RealCall.AsyncCall的execute()中执行Http请求的过程与RealCall.execute()中的过程有些类似:

调用getResponseWithInterceptorChain()执行网络请求并获得响应。
调用Callback回调通知用户执行的结果。可以看到这里对回调接口是同步调用,也就是回调方法将在后台线程中被调用。
调用client.dispatcher().finished(this)向client的dispatcher注销当前Call。

从上面对同步和异步请求流程的代码分析结果可以看出,都会调用拦截器链进行网络请求,即调用getResponseWithInterceptorChain方法来进行网络请求并得到请求结果。下面我们来看看getResponseWithInterceptorChain方法的具体实现:

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本身的官方文档解释:Interceptors area powerful mechanism that can monitor, rewrite, and retry calls.翻译过来就是拦截器可以用来监控,重试,重写请求的机制。通常情况下拦截器用来添加,移除或者转换请求或者回应的头部信息。拦截器是OkHttp中强大的流程装置,它可以用来监控log,修改请求,修改结果。

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    @Nullable Connection connection();

  }
}

拦截器接口中有intercept(Chain chain)方法,同时返回Response。所谓拦截器更像是AOP设计的一种实现。

在OkHttp中,内部维护了一个Interceptors的List,通过InterceptorChain进行多次拦截修改操作。RealInterceptorChain类中的proceed方法的代码如下所示:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    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);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

在RealInterceptorChain.proceed()中,除了对状态及获取的reponse做检查之外,最主要的事情即是构造新的RealInterceptorChain对象,获取对应Interceptor,并调用Interceptor的intercept(next)了。在这里,index充当迭代器或指示器的角色,用于指出当前正在处理的Interceptor。

Interceptor和RealInterceptorChain配合,实现类递归的形式,从最上层开始(Interceptor列表第一个)一层一层获取结果,而且每层对下层返回的结果又可以做处理,缓存包装等,实现了类似AOP的模式。

总结一下这几个Interceptor的职责:

RetryAndFollowUpInterceptor—>创建StreamAllocation对象,处理http的redirect,出错重试。对后续Interceptor的执行的影响:修改request及StreamAllocation。

BridgeInterceptor————–>补全缺失的一些http header。对后续Interceptor的执行的影响:修改request。

CacheInterceptor————–>处理http缓存。对后续Interceptor的执行的影响:若缓存中有所需请求的响应,则后续Interceptor不再执行。

ConnectInterceptor————>借助于前面分配的StreamAllocation对象建立与服务器之间的连接,并选定交互所用的协议是HTTP 1.1还是HTTP 2。对后续Interceptor的执行的影响:创建了httpStream和connection。

CallServerInterceptor———–>处理IO,与服务器进行数据交换。对后续Interceptor的执行的影响:为Interceptor链中的最后一个Interceptor,没有后续Interceptor。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值