OkHttp深入学习(三)——Cache、ConnectionPool、okio

    通过前面《OkHttp深入学习(一)——初探》和《OkHttp深入学习(二)——网络》两节的学习基本上对于okhttp的使用和实现有了一定的了解,不过还有一些比较重要的概念如缓存、ConnectionPool和OkHttpClient等都没有进行详细的说明。因此本节将介绍Cache和ConnectionPool的实现,以及前面两节遗漏下的部分内容进行补充,并在最后对okhttp项目学习进行一下的总结。

Request.class

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

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.
    第二节中出现了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);
      }
    };
  }
这一段代码表明在加载OkHttpClient类时就会对Internal.instance进行初始化操作。
internalCache() @OkHttpClient.class
InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
}
我们调用client.internalCache()就是使用的该方法,该方法返回cache.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。在我们构造一个Cache对象的时候,我们会默认执行如下语句。
[email protected]
final InternalCache internalCache = new InternalCache() {
    @Override public Response get(Request request) throws IOException {
      return Cache.this.get(request);
    }

    @Override public CacheRequest put(Response response) throws IOException {
      return Cache.this.put(response);
    }

    @Override public void remove(Request request) throws IOException {
      Cache.this.remove(request);
    }

    @Override public void update(Response cached, Response network) throws IOException {
      Cache.this.update(cached, network);
    }

    @Override public void trackConditionalCacheHit() {
      Cache.this.trackConditionalCacheHit();
    }

    @Override public void trackResponse(CacheStrategy cacheStrategy) {
      Cache.this.trackResponse(cacheStrategy);
    }
  };
即初始化Cache对象中的internalCache域,通过该internalCache对象对Cache中的同名方法进行操作,类似一个代理类。
setInternalCache()@OkHttpClient.class
该方法只能被本包中的类访问,在[email protected]中的setCache方法中被调用
void setInternalCache(InternalCache internalCache) {
      this.internalCache = internalCache;
      this.cache = null;
}
connectionPool()@OkHttpClient.class
public ConnectionPool connectionPool() {
    return connectionPool; 
}
connectionPool的初始化是在构建OkHttpClient时创建的,调用的构造器为new ConnectionPool();
首先我们先分析一下Cache类

Cache.class

该对象拥有一个DiskLruCache引用。
private final DiskLruCache cache; 
Cache()@Cache.class
public Cache(File directory, long maxSize) {
    this(directory, maxSize, FileSystem.SYSTEM);
  }
Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
其它方法如get、remove等暂时不介绍。
Cache的的具体工作都是交给DiskLruCache进行处理的,因此我们来分析一下DiskLruCache的工作原理;

DiskLruCache.class

private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true); LinkedHashMap自带Lru算法的光环属性,详情请看LinkedHashMap源码说明
该对象有一个线程池,不过该池最多有一个线程工作,用于清理,维护缓存数据。
create()@DiskLruCache.class
public static DiskLruCache create(FileSystem fileSystem, File directory, int appVersion,
      int valueCount, long maxSize) {
    if (maxSize <= 0) {
      throw new IllegalArgumentException("maxSize <= 0");
    }
    if (valueCount <= 0) {
      throw new IllegalArgumentException("valueCount <= 0");
    }
    // Use a single background thread to evict entries.
    Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true)); //创建一个最多容纳一条线程的线程池
    return new DiskLruCache(fileSystem, directory, appVersion, valueCount, maxSize, executor);
  }
OkHttpClient通过该方法获取到DiskLruCache的一个实例。DiskLruCache的构造器,只能被包内中类调用,因此一般都是通过该方法获取一个DiskLruCache实例。
DiskLruCache()@DiskLruCache.class
static final String JOURNAL_FILE = "journal";
static final String JOURNAL_FILE_TEMP = "journal.tmp";
static final String JOURNAL_FILE_BACKUP = "journal.bkp"
DiskLruCache(FileSystem fileSystem, File directory, int appVersion, int valueCount, long maxSize,
      Executor executor) {
    this.fileSystem = fileSystem;
    this.directory = directory;
    this.appVersion = appVersion;
    this.journalFile = new File(directory, JOURNAL_FILE);
    this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
    this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
    this.valueCount = valueCount;
    this.maxSize = maxSize;
    this.executor = executor;
  }
该构造器会在指定的目录下创建三个文件,这三个文件其实是一个工作日志文件  
get(key)@DiskLruCache.class
public synchronized Snapshot get(String key) throws IOException {
    initialize(); // note1
    checkNotClosed(); //判断是否可读
    validateKey(key); //note2
    Entry entry = lruEntries.get(key);
    if (entry == null || !entry.readable) return null;
    Snapshot snapshot = entry.snapshot();
    if (snapshot == null) return null;
    redundantOpCount++;
    journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n'); //note3
    if (journalRebuildRequired()) { //note4
      executor.execute(cleanupRunnable);  
    }
    return snapshot;
  }
1、方法内部就是从当前系统中的journalFileBackup和journalFile文件中选择一个journalFile作为操作对象。随后对文件中的内容进行验证判断和处理。最后如果正常则返回true,如果不正常则调用rebuildJournal()方法重新建立工作日志。日志的格式是有限制的,因此进行日志检查是防止外界对该文件进行修改,破坏日志结构。
2、判断key是否有效
3、向日志文件中写入读取日志
4、redundantOpCount >= redundantOpCompactThreshold && redundantOpCount >= lruEntries.size();简单说就是当前redundantOpCount值大于2000,而且该值大于等于存储的缓存键值对集合的容量。目的是判断日志中的数据是不是太多了?太多则开启线程执行清理工作

先来分析一下它是如何维护缓存数据的,先找到类中的cleanupRunnable对象,查看其run方法得知,其主要调用了trimToSize()rebuildJournal()两个方法对缓存数据进行维护的。
trimToSize()@DiskLruCache.class
private void trimToSize() throws IOException {
    while (size > maxSize) {
      Entry toEvict = lruEntries.values().iterator().next();
      removeEntry(toEvict);
    }
    mostRecentTrimFailed = false;
}
方法逻辑很简单,如果lruEntries的容量大于门限,则把lruEntries中第一个Entry移出集合,一直循环该操作,直到lruEntries的容量小于门限。
rebuildJournal()@DiskLruCache.class
if (journalWriter != null) {
      journalWriter.close();
    }
    BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp));
    try {
      writer.writeUtf8(MAGIC).writeByte('\n');
      writer.writeUtf8(VERSION_1).writeByte('\n');
      writer.writeDecimalLong(appVersion).writeByte('\n');
      writer.writeDecimalLong(valueCount).writeByte('\n');
      writer.writeByte('\n');
      for (Entry entry : lruEntries.values()) {
        if (entry.currentEditor != null) {
          writer.writeUtf8(DIRTY).writeByte(' ');
          writer.writeUtf8(entry.key);
          writer.writeByte('\n');
        } else {
          writer.writeUtf8(CLEAN).writeByte(' ');
          writer.writeUtf8(entry.key);
          entry.writeLengths(writer);
          writer.writeByte('\n');
        }
      }
    } finally {
      writer.close();
    }
    if (fileSystem.exists(journalFile)) {
      fileSystem.rename(journalFile, journalFileBackup);
    }
    fileSystem.rename(journalFileTmp, journalFile);
    fileSystem.delete(journalFileBackup);
    journalWriter = newJournalWriter();
    hasJournalErrors = false;
  }
方法作用就是重新创建一个journalFile文件,向其写入新的文件头,随后利用lruEntries数据向其写入缓存的内容。对于该部分的理解暂时就到这里了。下面来看看ConnectionPool是如何工作的。


ConnectionPool.class

private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
//该类创建了一个线程池,跟Dispatcher.class一样
final RouteDatabase routeDatabase = new RouteDatabase();
//该对象创建一个RouteDataBase对象,路由数据库,该数据库其实很简单,里面维护了一个private final Set<Route> failedRoutes = new LinkedHashSet<>();集合。存放失败的路由数据
private final Deque<RealConnection> connections = new ArrayDeque<>();
//这是ConnectionPool用于存储当前系统经历过三次握手可用的RealConnection
ConnectionPool()@ConnectionPool.class
public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;  //最大空闲连接数
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }
创建的连接池默认维护5条自由链接,自由链接保持之间5分钟。RealConnection是否是自由态由RealConnection中的Allocations集合的大小决定。如果RealConnection.noNewStream==true则表明该RealConnection拒绝为新StreamAllocation服务。
get()@ConnectionPool.class
RealConnection get(Address address, StreamAllocation streamAllocation) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.allocations.size() < connection.allocationLimit
          && address.equals(connection.route().address)
          && !connection.noNewStreams) {
        streamAllocation.acquire(connection);
        return connection;
      }
    }
    return null;
  }
从集合connections中能否得到一个可重复利用的RealConnection。
connection.allocations.size() < connection.allocationLimit;判断该RealConnection所服务的StreamAllocation数量,是否小于门限值。
address.equals(connection.route().address);该Connection的hostname地址等于要求get方法中的address值。
connection.noNewStreams可被分配给其它StreamAllocation。
streamAllocation.acquire(connection);等价于 connection.allocations.add(streamAllocation) ,将StreamAllocation添加到RealConnection的allocations集合中。增加RealConnection的引用计数。当该引用计数为0时考虑回收该RealConnection。

put()@ConnectionPool.class
void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }
对connections集合进行维护,同时将connection加入到该connections集合中。

cleanup()@ConnectionPool.class
long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;
    // Find either a connection to evict, or the time that the next eviction is due.
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
        // note1
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }
        idleConnectionCount++;
        // If the connection is ready to be evicted, we're done.
        long idleDurationNs = now - connection.idleAtNanos; //note 2
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      } // end of for
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) { //note3
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {
        //返回下次正常情况下次需要检查的等待时间
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        //返回下次正常情况下次需要检查的等待时间
        return keepAliveDurationNs;
      } else {
        //池中没有自由链接,也没有正在使用的链接
        cleanupRunning = false;
        return -1;
      }
    }
    closeQuietly(longestIdleConnection.socket()); //note4
    // Cleanup again immediately.
    return 0;
  }
Connectionpool维护其链接池中链接;该方法是在cleanupRunnable中的run方法中被调用。
1、缩减和获得与该Connection绑定的StreamAllocation数量,如果数量不为0,则判断为正在被使用的Connection,否则进行下面的步骤。
2、获取该Connection的自由时间,如果该链接自由时间超过当前系统所记录的Connection最长自由时间,则刷新当前记录最大值。这是标记过程
3、执行到这里,已经得到了当前系统空闲线程等待的最长时间,如果该时间大于系统最大自由时间或自由链接数大于系统所能维护的最大自由链接数,则将该RealConnection从链接池中移除出去。
4、执行到这里,表明刚刚有一个链接从连接池中被移出,此处将关闭该RealConnection对应的socket,即执行socket.close().
connectionBecameIdle@ConnectionPool.class
boolean connectionBecameIdle(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (connection.noNewStreams || maxIdleConnections == 0) {
      connections.remove(connection);
      return true;
    } else {
      notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
      return false;
    }
  }
该方法在StreamAllocation的deallocate中被调用。用于将connection回收,或者变成自由态。

Okio.class

该类主要任务是对通过socket得到的InputStream和OutputStream进行包装。转换成对应的Source和Sink对象。Sink和Source对象会被HttpStream使用。其实该部分对应一个单独的开源项目。对java.io和java.nio的一层包装,使得输入输出更加简单。下面贴出该类的两个source、sink静态方法。
source()@Okio.class
  public static Source source(final InputStream in) {
    return source(in, new Timeout());
  }
  private static Source source(final InputStream in, final Timeout timeout) {
    if (in == null) throw new IllegalArgumentException("in == null");
    if (timeout == null) throw new IllegalArgumentException("timeout == null");
    return new Source() {
      @Override public long read(Buffer sink, long byteCount) throws IOException {
        if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
        if (byteCount == 0) return 0;
        timeout.throwIfReached();
        Segment tail = sink.writableSegment(1);
        int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
        int bytesRead = in.read(tail.data, tail.limit, maxToCopy); //核心也就这句了
        if (bytesRead == -1) return -1;
        tail.limit += bytesRead;
        sink.size += bytesRead;
        return bytesRead;
      }
      @Override public void close() throws IOException {
        in.close();
      }
      @Override public Timeout timeout() {
        return timeout;
      }
      @Override public String toString() {
        return "source(" + in + ")";
      }
    };
  }

利用InputStream创建一个Source对象,利用该对象的read方法从InputStream中读取出数据。
 sink()@Okio.class
  public static Sink sink(final OutputStream out) {
    return sink(out, new Timeout());
  }
  private static Sink sink(final OutputStream out, final Timeout timeout) {
    if (out == null) throw new IllegalArgumentException("out == null");
    if (timeout == null) throw new IllegalArgumentException("timeout == null");
    return new Sink() {
      @Override public void write(Buffer source, long byteCount) throws IOException {
        checkOffsetAndCount(source.size, 0, byteCount);
        while (byteCount > 0) {
          timeout.throwIfReached();
          Segment head = source.head;
          int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
          out.write(head.data, head.pos, toCopy);
          head.pos += toCopy;
          byteCount -= toCopy;
          source.size -= toCopy;
          if (head.pos == head.limit) {
            source.head = head.pop();
            SegmentPool.recycle(head);
          }
        }
      }
      @Override public void flush() throws IOException {
        out.flush();
      }
      @Override public void close() throws IOException {
        out.close();
      }
      @Override public Timeout timeout() {
        return timeout;
      }
      @Override public String toString() {
        return "sink(" + out + ")";
      }
    };
  }
利用OutputStream创建一个Sink对象,利用该对象的write方法向OutputStream中写入数据。
一般情况下我们会利用Buffer对Sink和Source进行包装后再使用,source = Okio.buffer(Okio.source(rawSocket));  sink = Okio.buffer(Okio.sink(rawSocket)); 对于Okio的说明和使用参考链接

RouteSelector.class

  在StreamAllocation的findConnection方法中在构造RealConnection之前是需要获得一个route对象的,而route对象的获取是通过调用routeSelector的next方法来获取的。该route对象包含了我的url请求对应的ip地址和对应端口号。
  /* State for negotiating the next proxy to use. */
  private List<Proxy> proxies = Collections.emptyList();
  private int nextProxyIndex;
  /* State for negotiating the next socket address to use. */
  private List<InetSocketAddress> inetSocketAddresses = Collections.emptyList();
  private int nextInetSocketAddressIndex;
  private final List<Route> postponedRoutes = new ArrayList<>();
RouteSelector()@ RouteSelector.class
public RouteSelector(Address address, RouteDatabase routeDatabase) {
    this.address = address;
    this.routeDatabase = routeDatabase;
    resetNextProxy(address.url(), address.proxy());
}
在RouteSelector的构造器中会调用resetNextProxy方法参数为客户请求的url和对应的代理,一般情况一开始代理是空。该Address是在HttpEngine构建StreamAllocation时创建的,创建方法是调用HttpEngine的createAddress(client, request)方法,参数分别为OkHttpClient和Request。
resetNextProxy()@ RouteSelector.class
private void resetNextProxy(HttpUrl url, Proxy proxy) {
    if (proxy != null) {
      //note1
      proxies = Collections.singletonList(proxy);
    } else {
      proxies = new ArrayList<>();
      List<Proxy> selectedProxies = address.proxySelector().select(url.uri()); //note2
      if (selectedProxies != null) proxies.addAll(selectedProxies);
      // Finally try a direct connection. We only try it once!
      proxies.removeAll(Collections.singleton(Proxy.NO_PROXY));
      proxies.add(Proxy.NO_PROXY);
    }
    nextProxyIndex = 0;
}
1、如果代理不为空,则直接对proxies赋值
2、 address.proxySelector()等价于client.proxySelector()默认等价于ProxySelector.getDefault()等价于new java,net.ProxySelectorImpl();调用该对象的select方法获取该url对应的Proxy
下面我们就来看看next方法具体完成的操作。
next()@ RouteSelector.class
public Route next() throws IOException {
    if (!hasNextInetSocketAddress()) {
      if (!hasNextProxy()) {
        if (!hasNextPostponed()) {
          //前面三个队列都为空
          throw new NoSuchElementException();
        }//end if 3
        return nextPostponed();//返回最后一个之前失败的route
      }//end if 2
      lastProxy = nextProxy();//note1
    }//end if 1
    lastInetSocketAddress = nextInetSocketAddress(); //note 2
    Route route = new Route(address, lastProxy, lastInetSocketAddress);  //note 3
    if (routeDatabase.shouldPostpone(route)) {  //note 4
      postponedRoutes.add(route); //note 5
      return next(); //note 6
    }
    return route; //返回可以用的route
  }
1、首先运行到这里,获取下一个代理,同时刷新集合inetSocketAddresses的数据
2、获取到InetSocketAddress
3、利用前面得到的代理和SocketAddress构造一个Route
4、查找route是否存在于routeDatabase中,即检验生成的route是不是可用
5、step4返回真,该route加入到postponedRoutes集合中,如果最后所有的代理都试过了还是不行,则还会将该route重新再尝试一次
6、递归调用
nextProxy()@ RouteSelector.class
private Proxy nextProxy() throws IOException {
    if (!hasNextProxy()) {
      throw new SocketException("No route to " + address.url().host()
          + "; exhausted proxy configurations: " + proxies);
    }
    Proxy result = proxies.get(nextProxyIndex++);
    resetNextInetSocketAddress(result);
    return result;
}
从resetNextProxy()方法获取到的Proxies中得到一个Proxy
resetNextInetSocketAddress()@ RouteSelector.class
private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
    // Clear the addresses. Necessary if getAllByName() below throws!
    inetSocketAddresses = new ArrayList<>();
    String socketHost;
    int socketPort;
    if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
      socketHost = address.url().host();
      socketPort = address.url().port();
    } else {
      SocketAddress proxyAddress = proxy.address();
      if (!(proxyAddress instanceof InetSocketAddress)) {
        throw new IllegalArgumentException(
            "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
      }
      InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
      socketHost = getHostString(proxySocketAddress);
      socketPort = proxySocketAddress.getPort();
    }
    //获取得到Address对应的IP和Port
    if (socketPort < 1 || socketPort > 65535) {
      throw new SocketException("No route to " + socketHost + ":" + socketPort
          + "; port is out of range");
    }
    if (proxy.type() == Proxy.Type.SOCKS) {
      inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
    } else {
      // Try each address for best behavior in mixed IPv4/IPv6 environments.
      List<InetAddress> addresses = address.dns().lookup(socketHost);  //DNS查询的结果 url对应多个ip地址和端口号
      for (int i = 0, size = addresses.size(); i < size; i++) {
        InetAddress inetAddress = addresses.get(i);
        inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
      }
    }
    nextInetSocketAddressIndex = 0;
}
根据参数Proxy的值,得到一个java.net.InetSocketAddress类型对象,该独享有请求的ip和端口信息。

到此我们就分析完了整个okhttp开源项目中经常涉及到的一些概念。下面对过去三节的内容进行简单回顾下。
在创建OkHttpClient对象的时候创建了一个Dispatcher,一个Connection pool,以及一个由用户设置的缓存池。Dispatcher用于维护客户的异步请求和不同请求,连接池用于维护自由链接数,缓存池用于维护客户的网络请求响应
Dispatcher创建了一个线程池,两个任务队列,分别存储当前正在执行的请求和一个等待执行的异步请求,线程池的作用是执行异步请求。异步请求的调度是通过客户代码调用finish方法来手动调用的,不是通过线程池来自动检测。同步请求在客户发送请求的线程中执行,异步请求是在线程池中申请一个线程执行。
Connection pool 创建了一个线程池,用于维护池中的自由链接数,RealConnection采用引用计数的方法判断一个Connection是否是自由态,即如果RealConnection的Allocations集合为空则判断为自由态,采用标记清除的算法实现对废弃RealConnection的垃圾回收。当自由态链接数大于门限或者链接空闲时间超过预期值时则对该RealConnection资源进行回收,具体工作就是将RealConnection从ConnectionPool的connections集合中移出,底层调用socket.close()关闭网络连接。
Cache底层使用DiskLruCache对象对缓存进行实际的处理,缓存数据被包装成entry对象,DiskLruCache利用LinkedHashMap集合存储Entry,LinkedHashMap自带Lru算法性能,即将最近一次访问(如get put操作)的数据添加到集合的末尾,删除数据时是从集合的第一个位置往后删除,无需手动编写相关代码,直接利用其提供的性能即可。该对象同样创建了一个线程池,不过该线程池最多只能运行一条线程,线程用于对缓存数据进行维护,当前缓存数量大于门限时,将LinkedHashMap中前面的数据移出集合。DiskLruCache同样提供日志功能。
此外该开源项目还使用了Okio、缓存策略、路由代理选择等概念。该部分涉及到的内容跟具体的网络协议有关,在此就不再详细介绍了。到此为止,把OkHttp这个开源项目分析的也差不多了,感谢各位花时间阅读,有什么问题,希望下面留言,我们一同进步。


相关链接:
http://www.jianshu.com/p/aad5aacd79bf
https://github.com/square/okhttp






你可能感兴趣的:(cache,connectionpool,okhttp,okio,HTTP库)