OkHttp深入学习(一)——初探



    Android 4.4之后,HttpURLConnection底层实现已被OkHttp替换。可以见得OkHttp的性能已经被Google所认同。对于为何会想深入了解该库的原因:因为它的最底层走到了java的Socket;利用向Socket写入特定的Http协议数据包,实现网络通信。学习该开源项目,对于网络的学历大有益处,除此之外OkHttp使用了缓存和线程池概念。总之个人觉得OkHttp开源项目可以作为学习网络通信一篇很好的教科书

OkHttp的特点:

  • 支持HTTP2/SPDY黑科技
  • socket自动选择最好路线,并支持自动重连
  • 拥有自动维护的socket连接池,减少握手次数
  • 拥有队列线程池,轻松写并发
  • 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
  • 实现基于Headers的缓存策略

OkHttp的使用:

  1. 创建OkHttpClient对象:OkHttpClient client = new OkHttpClient();
  2. 创建网络请求:Request request = new Request.Builder()  .url("http://sethfeng.github.io/index.html") .build();
  3. 得到Call对象:Call call = client.newCall(request);  //实际创建的是一个RealCall对象,RealCall中有一个对client对象的引用
  4. 发送同步请求:Response response = call.excute();
  5. 发送异步请求:
    • call.enqueue(new Callback() {
          @Override
          public void onFailure(Request request, IOException e) {
              ...
          }
          @Override
          public void onResponse(Response response) throws IOException {
              ...
          }
      });


更多使用方法参考官方说明文档

OkHttpClient工作原理初步分析

RealCall.class

先来看一下通过 client.newCall(request)得到的Call对象
newCall(request)@OkHttpClient.class
Call newCall(Request request) {
    return new RealCall(this, request);
}
方法很简单就是利用调用newCall方法的OkHttpClient对象和newCall的输入参数构造一个RealCall对象。接下来看一下RealCall的构造器。
RealCall()@RealCall.class
protected RealCall(OkHttpClient client, Request originalRequest) {
    this.client = client;
    this.originalRequest = originalRequest;
  }
构造器也很简单,就是对RealCall中的OkHttpClient和Request域进行赋值。接下来看一下RealCall类中的execute和enqueue方法
public Response execute() throws IOException    
{
try {
            this.client.getDispatcher().executed(this); //同步请求不排队
             //方法内部会执行synchronized void executed(Call call) {   this.executedCalls.add(call);  }  即把这次请求加入到分发器里
            Response result = this.getResponseWithInterceptorChain(false);
            if(result == null) {
                throw new IOException("Canceled");
            }
            var2 = result;
 } finally {this.client.getDispatcher().finished(this); }
    ...
    return var2;
}
该方法完成一个同步请求,首先将这次的同步请求call添加到Dispatcher的工作队列中,随后调用getResponseWithInterceptorChain方法获取request对应的response。最后返回得到的response。
public void enqueue(Callback responseCallback) {
    enqueue(responseCallback, false);
 }
实际调用下面方法
void enqueue(Callback responseCallback, boolean forWebSocket) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket)); //异步请求会排队!
}
内部类[email protected]
final class AsyncCall extends NamedRunnable{
protected void execute() {
        Response response = getResponseWithInterceptorChain(forWebSocket);
        if (canceled) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
       this.client.getDispatcher().finished(this);  
    }
}
enqueue则是将请求包装成一个异步请求,异步请求继承自Runnable的子接口,实现了一个execute异步方法,Dispatcher会在合适的时间调用该方法,注意这里的execute方法和前面的execute不是同一个方法。在execute方法内部会调用 getResponseWithInterceptorChain方法获得网络请求的返回值,随后利用回调方法,将结果发送给客户。
Dispatcher负责对客户请求进行处理,因此接下来分析一下Dispatcher的execute和enqueue方法。将在下一节分析getResponseWithInterceptorChain方法的底层实现。

Dispatcher.class

OkHttpClient中有一个Dispatcher类型的域,在构造OkHttpClient的时候会调用new Dispatcher()方法获得一个Dispatcher对象,因此我们直接看Dispatcher的源码。
Dispatcher()@Dispatcher.class
public Dispatcher() {
}
看到这个方法一脸的懵逼。什么也没做?不要方,既然这里没有做任何工作,那么有两种可能,Dispatcher的相关域要么在类加载时或者声明时就进行了初始化,要么就会在使用的时候再临时进行初始化,即延迟初始化,这也是一种优化。那我们直接看execute方法和enqueue方法
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call); 
}
该方法很简单,就是将Recall请求添加到runningSyncCalls集合中。该集合在构造Dispatcher的时候就初始化好了Deque<RealCall> runningSyncCalls = new ArrayDeque<>();该集合代表着当前正在线程中运行的请求。有的童鞋是不是已经方了?这就完了?我们的请求在哪执行!!??不要忘了我们调用的RealCall的execute方法中,在this.client.getDispatcher().executed(this);语句后面继续调用Response result = this.getResponseWithInterceptorChain(false);方法,而实际的网络请求是在这里进行的。可以说Dispatch主要负责对客户的请求进行管理,留存,备份;重点在于利用线程池对异步请求的处理,同步请求它基本不干活。下面接着看enqueue方法。
enqueue()@Dispatcher.class
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      // maxRequestsPerHost默认为5 maxRequests默认64
      runningAsyncCalls.add(call);
      executorService().execute(call); //交给线程池去执行 call中的execute方法
    } else {
      readyAsyncCalls.add(call);  
      //存入等待队列
      //对于存入这里的请求,在方法promoteCalls()中会被取出,进行执行;
      //任务执行完成后,调用finished的promoteCalls()函数,不管是异步还是同步请求,它们在执行完execute方法过后都会调用Dispatcher的finished方法
    }
  }
这个方法代码相对于execute就多了一些代码,不过逻辑很简单,首先判断集合runningSyncCalls的大小,即当前运行的请求数是否小于maxRequest;同时判断该请求的hostname对应的的请求个数是否小于maxRequestsPerHost。如果条件全为真,则直接将该请求加入到集合runningSyncCalls中,随后调用executorService().execute(call);对异步任务进行处理。否则将异步请求加入到等待异步执行队列readyAsyncCalls中。下面看看executorService方法
executorService()@Dispatcher.class
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;
}
该方法返回了一个线程池,很熟悉吧;类似通过Executors.newCachedThreadPool()方法得到一个线程池。那么调用executorService().execute(call);方法最终会执行异步请求的execute方法,而异步请求的execute方法内部会调用getResponseWithInterceptorChain()方法获得response。
那么到此为止对于execute方法和enqueue方法的介绍就结束了。如果好奇点的童鞋,可能会问,如果异步请求被添加到readyAsyncCalls集合中,那么它何时会被执行呢?注意到,在RealCall的execute和enqueue方法执行完后都会执行this.client.getDispatcher().finished(this);  这样一条语句。那么我们来看下Dispatcher的finished方法。
finished()@Dispatcher.class
synchronized void finished(AsyncCall call) {
    if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
    promoteCalls();
}
方法很简单,将请求从runningAsyncCalls中移除出去,随后执行 promoteCalls()方法,接着看该方法源码
finished()@Dispatcher.class
private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }
在当前运行任务数大于maxRequests和等待执行异步任务数为空的两种情况下直接返回不进行任何操作。否则从等待执行异步请求集合中获取到请求,判断该请求的hostname对应的的请求个数是否小于maxRequestsPerHost,为真则将该任务从等待执行异步请求集合中移出,存入runningAsyncCalls集合中,最后调用线程池执行器执行该异步请求的execute方法。到此为止我们对于Dispatcher的介绍就到此为止了。
对Dispatcher的总结如下:
  • 该类中有两个集合分别为:runningAsyncCalls、readyAsyncCalls前者存放正在执行的请求,后者存放等待执行的请求
  • 该类中有一个newCachedThreadPool线程执行器,利用该执行器来执行异步请求的execute方法。也就是说异步请求发送在非当前工作线程,即创建异步请求的线程,而是从线程池中获取一条线程执行网络请求。同步请求则直接是在当前工作线程中执行。
  • 该类对异步请求的管理是通过maxRequests、maxRequestsPerHost进行控制的,前者控制线程池中同时运行的最大请求数,防止同时运行线程过多,造成OOM。后者限制了同一hostname下的请求数,防止一个应用占用的网络资源过多,优化用户体验。

文章的最后我们对okhttp中使用过程中遇到的Request、Response、OkHttpClient这几个类进行一下介绍。

Request.class

该类中有如下域
  private final HttpUrl url;  //目标地址
  private final String method; //方法
  private final Headers headers; //请求头,Headers.class里面维护了一个private final String[] namesAndValues;数据集
  private final RequestBody body; //请求表单
  private final Object tag; //标签
  private volatile URI javaNetUri; // Lazily initialized.
  private volatile CacheControl cacheControl; // Lazily initialized.
只能通过Builder方法构建Request对象。
public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
}
默认创建的是Get方法
public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
}
调用Request构造器,创建Request对象。

Response.class

类中有如下域
  private final Request request;  //对应的request
  private final Protocol protocol; //对应的Http协议
  private final int code; //返回状态码
  private final String message; //Http状态对应的消息
  private final Handshake handshake; //TLS握手协议Transport Layer Security
  private final Headers headers; //返回响应头
  private final ResponseBody body; //Http表单
  private Response networkResponse; //来源于网络的Response,如果响应来自缓存,则该值为null
  private Response cacheResponse; //来自缓存的响应
  private final Response priorResponse;  //在redirect或者授权改变的时候,该结果不为空
  private volatile CacheControl cacheControl; // Lazily initialized.
Okhttp中有client.internalCache()和client.connectionPool()两个重要的概念,前者管理网络访问的缓存信息,后者用于存储已链接的RealConnection(该RealConnection已经跟对应的hostname完成了三次握手)。下面我们看一下创建Cache和ConnectionPool这两个对象的OkHttpClient对象。

OkHttpClient.class

static {
    Internal.instance = new Internal() {
      @Override public void addLenient(Headers.Builder builder, String line) {
        builder.addLenient(line);
      }

      @Override public void addLenient(Headers.Builder builder, String name, String value) {
        builder.addLenient(name, value);
      }

      @Override public void setCache(OkHttpClient.Builder builder, InternalCache internalCache) {
        builder.setInternalCache(internalCache);
      }

      @Override public InternalCache internalCache(OkHttpClient client) {
        return client.internalCache();
      }

      @Override public boolean connectionBecameIdle(
          ConnectionPool pool, RealConnection connection) {
        return pool.connectionBecameIdle(connection);
      }

      @Override public RealConnection get(
          ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
        return pool.get(address, streamAllocation);
      }

      @Override public void put(ConnectionPool pool, RealConnection connection) {
        pool.put(connection);
      }

      @Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {
        return connectionPool.routeDatabase;
      }

      @Override
      public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) {
        ((RealCall) call).enqueue(responseCallback, forWebSocket);
      }

      @Override public StreamAllocation callEngineGetStreamAllocation(Call call) {
        return ((RealCall) call).engine.streamAllocation;
      }

      @Override
      public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {
        tlsConfiguration.apply(sslSocket, isFallback);
      }

      @Override public HttpUrl getHttpUrlChecked(String url)
          throws MalformedURLException, UnknownHostException {
        return HttpUrl.getChecked(url);
      }
    };
  }
这一段代码用static修饰,表明在加载OkHttpClient类时就会对Internal.instance进行初始化操作。
internalCache() @OkHttpClient.class
InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
}
cache域在我们构造OkHttpClient的时候是没有被初始化的,因此如果我们没有通过调用Builder的cache方法设置cache值的话,该方法返回的对象实际上是一个不支持任何缓存操作的对象,说着说该对象的所有方法为空。因此如果需要OkHttpClient支持缓存,需要我们写一个Cache对象并在构造OkHttpClient的时候将其传给OkHttpClient。
cache()@Builder.class@OkHttpClient.class
public Builder cache(Cache cache) {
      this.cache = cache;
      this.internalCache = null;
      return this;
}
完成cache的初始化,如果不调用该方法那么OkHttpClient默认不提供Cache功能。对于Cahce更为详细的介绍在后面的章节我们会进行详细介绍。《OkHttp深入学习(三)——Cache》
connectionPool()@OkHttpClient.class
public ConnectionPool connectionPool() {
    return connectionPool; 
}
connectionPool的初始化是在构建OkHttpClient时创建的,调用的构造器为new ConnectionPool()。对于ConnectionPool更为详细的介绍在后面的章节我们会进行详细介绍。OkHttp深入学习(二)——网络


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章