Android第三方库源码解析:OKHttp,快手安卓面试题

@Override protected void execute() {

  boolean signalledCallback = false;

  timeout.enter();

  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) {

    e = timeoutExit(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);

  }

}

}

复制代码




从上面的源码可以知道,拦截链的处理OKHttp帮我们默认做了五步拦截处理,其中RetryAndFollowUpInterceptor、BridgeInterceptor、CallServerInterceptor内部的源码很简洁易懂,此处不再多说,下面将对OKHttp最为核心的两部分:缓存处理和连接处理(连接池)进行讲解。



### []( )二、网络请求缓存处理之CacheInterceptor



@Override public Response intercept(Chain chain) throws IOException {

// 根据request得到cache中缓存的response

Response cacheCandidate = cache != null

    ? cache.get(chain.request())

    : null;



long now = System.currentTimeMillis();



// request判断缓存的策略,是否要使用了网络,缓存或两者都使用

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(),     cacheCandidate).get();

Request networkRequest = strategy.networkRequest;

Response cacheResponse = strategy.cacheResponse;



if (cache != null) {

  cache.trackResponse(strategy);

}



if (cacheCandidate != null && cacheResponse == null) {

  closeQuietly(cacheCandidate.body()); // The cache   candidate wasn't applicable. Close it.

}



// If we're forbidden from using the network and the cache is insufficient, fail.

if (networkRequest == null && cacheResponse == null) {

  return new Response.Builder()

      .request(chain.request())

      .protocol(Protocol.HTTP_1_1)

      .code(504)

      .message("Unsatisfiable Request (only-if-cached)")

      .body(Util.EMPTY_RESPONSE)

      .sentRequestAtMillis(-1L)

      .receivedResponseAtMillis(System.currentTimeMillis())

      .build();

}



// If we don't need the network, we're done.

if (networkRequest == null) {

  return cacheResponse.newBuilder()

      .cacheResponse(stripBody(cacheResponse))

      .build();

}



Response networkResponse = null;

try {

    // 调用下一个拦截器,决定从网络上来得到response

    networkResponse = chain.proceed(networkRequest);

} finally {

    // If we're crashing on I/O or otherwise,   don't leak the cache body.

    if (networkResponse == null && cacheCandidate != null) {

      closeQuietly(cacheCandidate.body());

    }

}



// If we have a cache response too, then we're doing a conditional get.

// 如果本地已经存在cacheResponse,那么让它和网络得到的networkResponse做比较,决定是否来更新缓存的cacheResponse

if (cacheResponse != null) {

    if (networkResponse.code() == HTTP_NOT_MODIFIED)   {

      Response response = cacheResponse.newBuilder()

              .headers(combine(cacheResponse.headers(), networkResponse.headers()))

              .sentRequestAtMillis(networkResponse.sentRequestAtMillis())

              .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())

              .cacheResponse(stripBody(cacheResponse))

              .networkResponse(stripBody(networkResponse))

          .build();

      networkResponse.body().close();



      // Update the cache after combining headers but before stripping the

      // Content-Encoding header (as performed by initContentStream()).

      cache.trackConditionalCacheHit();

      cache.update(cacheResponse, response);

      return response;

    } else {

      closeQuietly(cacheResponse.body());

    }

}



Response response = networkResponse.newBuilder()

    .cacheResponse(stripBody(cacheResponse))

    .networkResponse(stripBody(networkResponse))

    .build();



if (cache != null) {

  if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response,   networkRequest)) {

    // Offer this request to the cache.

    // 缓存未经缓存过的response

    CacheRequest cacheRequest = cache.put(response);

    return cacheWritingResponse(cacheRequest, response);

  }



  if (HttpMethod.invalidatesCache(networkRequest.method())) {

    try {

      cache.remove(networkRequest);

    } catch (IOException ignored) {

      // The cache cannot be written.

    }

  }

}



return response;

}

复制代码




缓存拦截器会根据请求的信息和缓存的响应的信息来判断是否存在缓存可用,如果有可以使用的缓存,那么就返回该缓存给用户,否则就继续使用责任链模式来从服务器中获取响应。当获取到响应的时候,又会把响应缓存到磁盘上面。



### []( )三、ConnectInterceptor之连接池



@Override public Response intercept(Chain chain) throws IOException {

RealInterceptorChain realChain = (RealInterceptorChain) chain;

Request request = realChain.request();

StreamAllocation streamAllocation = realChain.streamAllocation();



// We need the network to satisfy this request.     Possibly for validating a conditional GET.

boolean doExtensiveHealthChecks = !request.method().equals("GET");

// HttpCodec是对 HTTP 协议操作的抽象,有两个实现:Http1Codec和Http2Codec,顾名思义,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。在这个方法的内部实现连接池的复用处理

HttpCodec httpCodec = streamAllocation.newStream(client, chain,     doExtensiveHealthChecks);

RealConnection connection = streamAllocation.connection();



return realChain.proceed(request, streamAllocation, httpCodec, connection);

}

// Returns a connection to host a new stream. This // prefers the existing connection if it exists,

// then the pool, finally building a new connection.

// 调用 streamAllocation 的 newStream() 方法的时候,最终会经过一系列

// 的判断到达 StreamAllocation 中的 findConnection() 方法

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,

int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {

  ...



  // Attempt to use an already-allocated connection. We need to be careful here because our

  // already-allocated connection may have been restricted from creating new streams.

  // 尝试使用已分配的连接,已经分配的连接可能已经被限制创建新的流

  releasedConnection = this.connection;

  // 释放当前连接的资源,如果该连接已经被限制创建新的流,就返回一个Socket以关闭连接

  toClose = releaseIfNoNewStreams();

  if (this.connection != null) {

    // We had an already-allocated connection and it's good.

    result = this.connection;

    releasedConnection = null;

  }

  if (!reportedAcquired) {

    // If the connection was never reported acquired, don't report it as released!

    // 如果该连接从未被标记为获得,不要标记为发布状态,reportedAcquired 通过 acquire()   方法修改

    releasedConnection = null;

  }



  if (result == null) {

    // Attempt to get a connection from the pool.

    // 尝试供连接池中获取一个连接

    Internal.instance.get(connectionPool, address, this, null);

    if (connection != null) {

      foundPooledConnection = true;

      result = connection;

    } else {

      selectedRoute = route;

    }

  }

}

// 关闭连接

closeQuietly(toClose);



if (releasedConnection != null) {

  eventListener.connectionReleased(call, releasedConnection);

}

if (foundPooledConnection) {

  eventListener.connectionAcquired(call, result);

}

if (result != null) {

  // If we found an already-allocated or pooled connection, we're done.

  // 如果已经从连接池中获取到了一个连接,就将其返回

  return result;

}



// If we need a route selection, make one. This   is a blocking operation.

boolean newRouteSelection = false;

if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {

  newRouteSelection = true;

  routeSelection = routeSelector.next();

}



synchronized (connectionPool) {

  if (canceled) throw new IOException("Canceled");



  if (newRouteSelection) {

    // Now that we have a set of IP addresses,   make another attempt at getting a   connection from

    // the pool. This could match due to   connection coalescing.

     // 根据一系列的 IP地址从连接池中获取一个链接

    List<Route> routes = routeSelection.getAll();

    for (int i = 0, size = routes.size(); i < size;i++) {

      Route route = routes.get(i);

      // 从连接池中获取一个连接

      Internal.instance.get(connectionPool, address, this, route);

      if (connection != null) {

        foundPooledConnection = true;

        result = connection;

        this.route = route;

        break;

      }

    }

  }



  if (!foundPooledConnection) {

    if (selectedRoute == null) {

      selectedRoute = routeSelection.next();

    }



    // Create a connection and assign it to this allocation immediately. This makes it   possible

    // for an asynchronous cancel() to interrupt the handshake we're about to do.

    // 在连接池中如果没有该连接,则创建一个新的连接,并将其分配,这样我们就可以在握手之前进行终端

    route = selectedRoute;

    refusedStreamCount = 0;

    result = new RealConnection(connectionPool, selectedRoute);

    acquire(result, false);

  }

}

// If we found a pooled connection on the 2nd time around, we're done.

if (foundPooledConnection) {

// 如果我们在第二次的时候发现了一个池连接,那么我们就将其返回

  eventListener.connectionAcquired(call, result);

  return result;

}



// Do TCP + TLS handshakes. This is a blocking     operation.

 // 进行 TCP 和 TLS 握手

result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,

  connectionRetryEnabled, call, eventListener);

routeDatabase().connected(result.route());



Socket socket = null;

synchronized (connectionPool) {

  reportedAcquired = true;



  // Pool the connection.

  // 将该连接放进连接池中

  Internal.instance.put(connectionPool, result);



  // If another multiplexed connection to the same   address was created concurrently, then

  // release this connection and acquire that one.

  // 如果同时创建了另一个到同一地址的多路复用连接,释放这个连接并获取那个连接

  if (result.isMultiplexed()) {

    socket = Internal.instance.deduplicate(connectionPool, address, this);

    result = connection;

  }

}

closeQuietly(socket);



eventListener.connectionAcquired(call, result);

return result;

}

复制代码




从以上的源码分析可知:



*   判断当前的连接是否可以使用:流是否已经被关闭,并且已经被限制创建新的流;

*   如果当前的连接无法使用,就从连接池中获取一个连接;

*   连接池中也没有发现可用的连接,创建一个新的连接,并进行握手,然后将其放到连接池中。



在从连接池中获取一个连接的时候,使用了 Internal 的 get() 方法。Internal 有一个静态的实例,会在 OkHttpClient 的静态代码快中被初始化。我们会在 Internal 的 get() 中调用连接池的 get() 方法来得到一个连接。并且,从中我们明白了连接复用的一个好处就是省去了进行 TCP 和 TLS 握手的一个过程。因为建立连接本身也是需要消耗一些时间的,连接被复用之后可以提升我们网络访问的效率。



接下来,我们来详细分析下ConnectionPool是如何实现连接管理的。



OkHttp 的缓存管理分成两个步骤,一边当我们创建了一个新的连接的时候,我们要把它放进缓存里面;另一边,我们还要来对缓存进行清理。在 ConnectionPool 中,当我们向连接池中缓存一个连接的时候,只要调用双端队列的 add() 方法,将其加入到双端队列即可,而清理连接缓存的操作则交给线程池来定时执行。



private final Deque connections = new ArrayDeque<>();

void put(RealConnection connection) {

assert (Thread.holdsLock(this));

if (!cleanupRunning) {

  cleanupRunning = true;

  // 使用线程池执行清理任务

  executor.execute(cleanupRunnable);

}

// 将新建的连接插入到双端队列中

connections.add(connection);

}

private final Runnable cleanupRunnable = new Runnable() {

@Override public void run() {

while (true) {

    // 内部调用 cleanup() 方法来清理无效的连接

    long waitNanos = cleanup(System.nanoTime());

    if (waitNanos == -1) return;

    if (waitNanos > 0) {

      long waitMillis = waitNanos / 1000000L;

      waitNanos -= (waitMillis * 1000000L);

      synchronized (ConnectionPool.this) {

        try {

          ConnectionPool.this.wait(waitMillis, (int) waitNanos);

        } catch (InterruptedException ignored) {

        }

      }

    }

}

};

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



      // If the connection is in use, keep     searching.

      // 遍历所有的连接

      if (pruneAndGetAllocationCount(connection, now) > 0) {

        inUseConnectionCount++;

        continue;

      }



      idleConnectionCount++;



      // If the connection is ready to be     evicted,     we're done.

      // 如果找到了一个可以被清理的连接,会尝试去寻找闲置时间最久的连接来释放

      long idleDurationNs = now - connection.idleAtNanos;

      if (idleDurationNs > longestIdleDurationNs) {

        longestIdleDurationNs = idleDurationNs;

        longestIdleConnection = connection;

      }

    }

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

由于题目很多整理答案的工作量太大,所以仅限于提供知识点,详细的很多问题和参考答案我都整理成了 PDF文件

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-IX9AqmcD-1712123920588)]

最后

由于题目很多整理答案的工作量太大,所以仅限于提供知识点,详细的很多问题和参考答案我都整理成了 PDF文件

[外链图片转存中…(img-xgglUjsH-1712123920588)]

[外链图片转存中…(img-H2iZXHU2-1712123920589)]

[外链图片转存中…(img-kxDoTIuH-1712123920589)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值