Anroid 网络请求 与 OkHttp

这篇文章主要讲 Android 网络请求时所使用到的各个请求库的关系,以及 OkHttp3 的介绍。(如理解有误,请大家帮忙指出)

建议先对 HTTP 协议有一个了解,「ruanyifeng」大神文章

HTTP 协议入门

互联网协议入门(一)

互联网协议入门(二)

简介

开始一直不是很清楚各个 Android 网络库的本质区别,Android-async-http、HttpClient、HttpUrlConnection、Volley、OkHttp、Retrofit...等等,我们可以使用它们实现 Android 端的网络请求,但是它们却又与其他的库略有不同

我们知道,要想实现网络请求,就要发送我们的请求到服务器端,并接收响应。其中,上面所写的库中,HttpClient、HttpUrlConnection、OkHttp 是实现了 Http 协议的,可以帮助我们发送请求,接收响应,我们称之为 Http 客户端,而 Android-async-http、Volley、Retrofit 只是以这三个 Http 客户端为底层实现,进而封装的请求库,这就是它们的区别

Android Http 客户端

Http 客户端:可以发出请求并接收响应,实现了 HTTP 协议。至于怎么实现,大致就是通过数据流传递 Request 信息,并在接收到 Response 数据流后,进行解析。那数据流是怎么发送呢,请返回顶端,看「ruanyifeng」大神的文章,这里面涉及到的是 DNS、TCP/IP、MAC 地址等信息,不在本文探讨范围之内。

Android 相关的 Http 客户端发展到目前为止,以 HttpClientHttpUrlConnectionOkHttp 为主。

  • HttpClient:Apache 出品,Android 不建议使用,SDK 6.0 已经删除了相关类。

  • HttpUrlConnection:轻量极的 HTTP 客户端,API 简单,易扩展,不过从 Android4.4 开始 HttpURLConnection 的底层实现采用的是 OkHttp。

  • OkHttp:高性能的 http 库,支持同步、异步,而且实现了 spdy、http2、websocket 协议,api 简洁易用。

其实我们甚至可以自己来实现 HTTP 协议,写一个 HTTP 客户端。

Android 网络请求库

网络请求库是对 Android Http 客户端进行了进一步封装,当然,称其为网络请求库是为了与上面三个区分开来。网络请求库也可以称为 HTTP 客户端,它有客户端的所要求的功能,但是其底层使用的就是上面三个客户端其中的一种(基本上),虽然直接使用 Android Http 客户端请求网络亦无不可,不过封装之后则更加方便我们使用

  • Android-async-http:异步网络库,底层使用的是 HttpClient,但是 Android 不建议使用 HttpClient,因此,该库已不适合在 Android 开发中使用

  • Volley:异步网络库,在 Android 2.3 及以上版本,使用的是 HttpURLConnection,而在Android 2.2 及以下版本,使用的是 HttpClient。Btw,Volley 已经停止了更新。

  • Retrofit:一个 RESTful 的 HTTP 网络请求框架的封装。从 Retrofit 2.0 开始,内置 OkHttp,前者专注于接口的封装,后者专注于网络请求的高效

  • ...

当然还有很多网络请求库,就不一一列举了。

OkHttp

OkHttp 优势

为什么说 OkHttp 高效呢?来看一下其官方介绍。

OkHttp 官方介绍:

HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.

OkHttp is an HTTP client that’s efficient by default:

  • HTTP/2 support allows all requests to the same host to share a socket.
  • Connection pooling reduces request latency (if HTTP/2 isn’t available).
  • Transparent GZIP shrinks download sizes.
  • Response caching avoids the network completely for repeat requests.

OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails.

Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks.

OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7.

OkHttp 是一个高效的 HTTP 客户端:

  • 支持 HTTP/2 ,共享同一个Socket来处理同一个服务器的所有请求
  • 如果 HTTP/2 不可用,则通过连接池来减少请求延时
  • 无缝的支持 GZIP 来减少数据流量
  • 缓存响应数据来减少重复的网络请求

OkHttp 会从很多常用的连接问题中自动恢复。如果你的服务器配置了多个 IP 地址,当第一个 IP 连接失败的时候,OkHttp 会自动尝试下一个 IP。OkHttp 还处理了代理服务器问题和 SSL 握手失败问题。

使用 OkHttp 无需重写程序中的网络代码。OkHttp 实现了几乎和 HttpURLConnection 一样的 API 。如果使用了 Apache HttpClient,OkHttp 也提供了一个对应的 okhttp-apache 模块。

OkHttp 支持 Android 2.3 以上版本,java 最低要求 1.7 版本

摘自:http://frodoking.github.io/2015/03/12/android-okhttp/

OkHttp 从调用看源码

简单调用:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

看一下调用过程

  1. 创建 OkHttpClient 对象
  2. 创建 Request 对象
  3. 通过 OkHttpClient 对象调用 newCall() 方法,传入创建好的 Request 对象
  4. 执行 execute() 方法,得到 Response 对象。

OkHttpClient:

/**
 *  Factory for calls, which can be used to send HTTP requests and read their responses.
 *  ...
 *  OkHttp performs best when you create a single OkHttpClient instance
 *  ...
 */
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory{

...

/**
 * Prepares the {@code request} to be executed at some point in the future.
 */
@Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
}
    
/**
 * Uses {@code request} to connect a new web socket.
 */
@Override public WebSocket newWebSocket(Request request, WebSocketListener listener) {
    RealWebSocket webSocket = new RealWebSocket(request, listener, new Random());
    webSocket.connect(this);
    return webSocket;
}

...

}

我们可以看到 源码中 OkHttpClient 实现了 Call.FactoryWebSocket.Factory 的接口,就是说,它可以新建一个 Call ,也可以新建一个 web socket.

Factory for calls, which can be used to send HTTP requests and read their responses.
...

Call 的工厂类,Call 用来发送 Http 请求,并读取响应

OkHttp performs best when you create a single OkHttpClient instance

同时,它实现了 Cloneable 借口,源码中 javadoc 里建议全局只有一个 OkHttpClient 实例,如果我们使用 clone() 方法得到一个新的实例,这个新实例的变化不会影响到原来的实例。

暂且不管 OkHttpClient 中其他属性和方法,我们再来看看 Request

Request:

通过 Builder 模式,可以设置请求的 url、请求类型 method、请求头域 headers、请求体 body、请求 tag、缓存要求 cacheControl

新建 Call 对象时,传入该对象,根据设置的属性发起请求。

再来看看 Call

Call:

/**
 * A call is a request that has been prepared for execution. A call can be canceled. As this object
 * represents a single request/response pair (stream), it cannot be executed twice.
 */
public interface Call extends Cloneable {

    // 返回创建 Call 时所传入的 request
    Request request();
    
    // 执行请求(同步)
    Response execute() throws IOException;
    
    // 执行请求(异步)
    void enqueue(Callback responseCallback);
    
    // 取消请求
    void cancel();
    
    // 请求是否正在执行
    boolean isExecuted();
    
    // 请求是否取消
    boolean isCanceled();
    
    // 克隆 Call 实例
    Call clone();
    
    interface Factory {
        Call newCall(Request request);
    }
}

通过 Callexecute()enqueue(responseCallback) 方法发起请求。

RealCallCall 的实现类,我们来看一下关于 execute() 方法的实现。

@Override public Response execute() throws IOException {
    // 判断 execute()  方法是否已经被执行
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    // 获取 Call 的堆栈信息,设置到 RetryAndFollowUpInterceptor 对象中,在里面创建 StreamAllocation 时用到
    // RetryAndFollowUpInterceptor:请求失败重试和重定向拦截器
    captureCallStackTrace();
    try {
        // 将该请求放入 Dispatcher 中,会加入一个 Deque(双向队列) 中,最后由 Dispatcher 分派请求任务
        client.dispatcher().executed(this);
        // 通过拦截器后,获取到 Response
        Response result = getResponseWithInterceptorChain(); 
        if (result == null) throw new IOException("Canceled");
        return result;
    } finally {
        client.dispatcher().finished(this);
    }
}

名词解释:

拦截器:观察,修改以及可能短路的请求输出和响应请求的回来。通常情况下拦截器用来添加,移除或者转换请求或者回应的头部信息。

拦截器可以控制在请求前后做一些操作,如打印日志等。

Dispatcher:异步请求策略,可以设置每个主机最大请求数(默认为5),和最大并发请求数(默认是64)。

其实不论是同步还是异步请求,都会经过 Dispatcher,不同点在于

  • 同步

Dispatcher会在同步执行任务队列中记录当前被执行过得任务Call,同时在当前线程中去执行Call的getResponseWithInterceptorChain()方法,直接获取当前的返回数据Response;

  • 异步

首先来说一下Dispatcher,Dispatcher内部实现了懒加载无边界限制的线程池方式,同时该线程池采用了SynchronousQueue这种阻塞队列。SynchronousQueue每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此此队列内部其 实没有任何一个元素,或者说容量是0,严格说并不是一种容器。由于队列没有容量,因此不能调用peek操作,因为只有移除元素时才有元素。显然这是一种快速传递元素的方式,也就是说在这种情况下元素总是以最快的方式从插入者(生产者)传递给移除者(消费者),这在多任务队列中是最快处理任务的方式。对于高频繁请求的场景,无疑是最适合的。

异步执行是通过Call.enqueue(Callback responseCallback)来执行,在Dispatcher中添加一个封装了Callback的Call的匿名内部类Runnable来执行当前的Call。这里一定要注意的地方这个AsyncCall是Call的匿名内部类。AsyncCall的execute方法仍然会回调到Call的getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。

摘自:http://frodoking.github.io/2015/03/12/android-okhttp/

继续往下看,调用了 getResponseWithInterceptorChain() 获得 response,这个方法中,添加了各种拦截器,并调用 proceed() 开始执行拦截器

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    // 将所有的拦截器添加到 list 中,通过 Chain 的 chain.proceed(request) 方法开始执行拦截器
    // 同时,所有的拦截器,除 CallServerInterceptor 外,都要调用 proceed() 方法,它是所有拦截器顺序调用的关键,下面会解释
    List interceptors = new ArrayList<>();
    
    // 添加创建 OkHttpClient 时设置的自定义拦截器
    interceptors.addAll(client.interceptors());
    // 请求失败重试和重定向拦截器
    interceptors.add(retryAndFollowUpInterceptor);
    // 补全缺失的一些http header。对后续Interceptor的执行的影响主要为修改了Request。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // 处理http缓存。对后续Interceptor的执行的影响为,若缓存中有所需请求的响应,则后续Interceptor不再执行。
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // 借助于前面分配的StreamAllocation对象建立与服务器之间的连接,并选定交互所用的协议是HTTP 1.1还是HTTP 2。对后续Interceptor的执行的影响为,创建了HttpStream和connection。
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        // 添加网络拦截器(上面是应用拦截器)
        interceptors.addAll(client.networkInterceptors());
    }
    // Interceptor链中的最后一个Interceptor,用于处理IO,与服务器进行数据交换(不调用 proceed() 方法的拦截器,完成之后返回 Response)
    interceptors.add(new CallServerInterceptor(forWebSocket));
    
    // 生成 Chain 对象,调用 proceed() 方法,在拦截器的实现中,会新建 Chain 对象,同时也会调用 proceed() 方法,类似递归的形式
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
}

我们再来看 Chain 对象中的 proceed() 方法

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

    ...
            
    // Call the next interceptor in the chain.
    // 咚咚咚 敲黑板,这边就是拦截器顺序调用的核心
    // 新建一个 Chain 对象,获取下一个拦截器,调用拦截器的 intercept() 方法
    // 在拦截器实现的该方法中会再次调用 proceed() 方法,直到最后一个拦截器返回 response
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    
    ...
    
    return response;
}

在最后的拦截器 CallServerInterceptor 中,将会请求服务器,返回 response。

以上是同步请求的过程,异步请求过程类似,不同点在于 Dispatcher 分派任务,异步请求使用 enqueue() 方法

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    // 添加任务到异步 Deque 中,会根据当前请求情况,决定是否当即进行请求
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

Dispatcher.class

synchronized void enqueue(AsyncCall call) {
    //  判断请求数等是否达到上限
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        runningAsyncCalls.add(call);
        // 通过 ExecutorService 执行请求
        executorService().execute(call);
    } else {
        readyAsyncCalls.add(call);
    }
}

RealCall.class(RealCall.AsyncCall)

final class AsyncCall extends NamedRunnable {

    ...
    
    // 异步请求执行后会调用到该方法
    @Override protected void execute() {
        boolean signalledCallback = false;
        try {
            // 同样通过 getResponseWithInterceptorChain() 方法获取 response
            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 {
                responseCallback.onFailure(RealCall.this, e);
            }
        } finally {
            client.dispatcher().finished(this);
        }
    }
    
}

以上就是 OkHttp 调用过程,比较简单,基本上就是调用 Call 执行方法,将请求添加到 Dispatcher,根据是否缓存从不同的源获取数据,又根据同步还是异步,分别执行 execute() 返回 response 或者是添加到请求队列,通过回调获取 response

了解调用过程后,我们再分析其他 OkHttp 特性就有了方向

如 OkHttp 的失败重连及重定向机制,可以从 RetryAndFollowUpInterceptor 入手,这个拦截器就是用来实现该功能的

如 缓存策略,我们可以从 CacheInterceptor 入手,其中用到了 CacheStrategy 类,可以确定我们是用缓存数据、网络数据还是两个皆有,我们可以看这个类入手来了解 OkHttp 的缓存机制

等等

初研究 Android 网络请求 与 OkHttp,记于此。

参考:

http://www.jianshu.com/p/2fa728c8b366

OkHttp-Wiki

OkHttp-Wiki-译()

http://frodoking.github.io/2015/03/12/android-okhttp/

http://www.jianshu.com/p/5c98999bc34f

你可能感兴趣的:(Anroid 网络请求 与 OkHttp)