首页 > 其他分享 >Android 面试 | 开源库面试题

Android 面试 | 开源库面试题

时间:2023-06-22 11:07:43浏览次数:34  
标签:面试题 缓存 return get 开源 new Android null public


1,HTTP与缓存理论

1.1 HTTP缓存策略

HTTP的缓存机制也是依赖于请求和响应header里的参数类实现的,最终的响应结果是从缓存还是从服务端拉取是有一套完整的机制的,HTTP的缓存机制的流程如下所示。

Android 面试 | 开源库面试题_面试

HTTP的缓存可以分为两种:强制缓存和对比缓存

1.2 强制缓存

要服务端参与判断是否继续使用缓存,当客户端第一次请求数据是,服务端返回了缓存的过期时间(Expires与Cache-Control),没有过期就可以继续使用缓存,否则则不适用,无需再向服务端询问。

强制缓存使用的的两个标识:

  • Expires:Expires的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。到期时间是服务端生成的,客户端和服务端的时间可能有误差。
  • Cache-Control:Expires有个时间校验的问题,所以HTTP1.1采用Cache-Control替代Expires。

其中,Cache-Control的取值有如下一些:

  • private::客户端可以缓存。
  • public::客户端和代理服务器都可缓存。
  • max-age=xxx: 缓存的内容将在 xxx 秒后失效
  • no-cache::需要使用对比缓存来验证缓存数据。
  • no-store:所有内容都不会缓存;强制缓存,对比缓存都不会触发。

1.3 对比缓存

需要服务端参与判断是否继续使用缓存,当客户端第一次请求数据时,服务端会将缓存标识(Last-Modified/If-Modified-Since与Etag/If-None-Match)与数据一起返回给客户端,客户端将两者都备份到缓存中 ,再次请求数据时,客户端将上次备份的缓存标识发送给服务端,服务端根据缓存标识进行判断,如果返回304,则表示通知客户端可以继续使用缓存。

对比缓存的两个标识:

  • Last-Modified:表示资源上次修改的时间。当客户端发送第一次请求时,服务端返回资源上次修改的时间,返回格式例子如下:Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT
  • If-Modified-Since:服务端接收到客户端发来的资源修改时间,与自己当前的资源修改时间进行对比,如果自己的资源修改时间大于客户端发来的资源修改时间,则说明资源做过修改, 则返回200表示需要重新请求资源,否则返回304表示资源没有被修改,可以继续使用缓存。不同于If-Unmodified-Since,If-Modified-Since只能与GET或HEAD一起使用。与组合使用时If-None-Match,将被忽略,除非服务器不支持If-None-Match。

2,OKHttp

现在主流的Android网络请求框架有OKHttp和Retrofit,不过,Retrofit顶层使用的也是OKHttp。在正式介绍OKHttp之前,来看一下Http常见的一些状态码:

  • 100~199:指示信息,表示请求已接收,继续处理。
  • 200~299:请求成功,表示请求已被成功接收、理解。
  • 300~399:重定向,要完成请求必须进行更进一步的操作。
  • 400~499:客户端错误,请求有语法错误或请求无法实现。
  • 500~599:服务器端错误,服务器未能实现合法的请求。

2.1 OKHttp请求流程

下面是OKHttp内部发起请求的大致流程图,如下图。

Android 面试 | 开源库面试题_面试_02

下面是使用OKHttp进行Get请求的代码。

//1.新建OKHttpClient客户端
OkHttpClient client = new OkHttpClient();
//新建一个Request对象
Request request = new Request.Builder()
        .url(url)
        .build();
//2.Response为OKHttp中的响应
Response response = client.newCall(request).execute();

可以看到,使用OKHttp进行请求时,只需要创建一个OKHttpClient对象和Request对象,然后再调用execute()方法和enqueue()方法即可。其中,execute()方法是同步方法,enqueue()方法是异步方法。

2.2 OKHttpClient

使用OKHttpClient之前,需要先创建一个OKHttpClient客户端,OKHttpClient的构造方法如下。

OkHttpClient client = new OkHttpClient();

public OkHttpClient() {
    this(new Builder());
}

OkHttpClient(Builder builder) {
    ....
}

可以看到,OkHttpClient使用了建造者模式,Builder里面的可配置参数如下。

public static final class Builder {
    Dispatcher dispatcher;// 分发器
    @Nullable Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;// 传输层版本和连接协议
    final List<Interceptor> interceptors = new ArrayList<>();// 拦截器
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    EventListener.Factory eventListenerFactory;
    ProxySelector proxySelector;
    CookieJar cookieJar;
    @Nullable Cache cache;
    @Nullable InternalCache internalCache;// 内部缓存
    SocketFactory socketFactory;
    @Nullable SSLSocketFactory sslSocketFactory;// 安全套接层socket 工厂,用于HTTPS
    @Nullable CertificateChainCleaner certificateChainCleaner;// 验证确认响应证书 适用 HTTPS 请求连接的主机名。
    HostnameVerifier hostnameVerifier;// 验证确认响应证书 适用 HTTPS 请求连接的主机名。  
    CertificatePinner certificatePinner;// 证书锁定,使用CertificatePinner来约束哪些认证机构被信任。
    Authenticator proxyAuthenticator;// 代理身份验证
    Authenticator authenticator;// 身份验证
    ConnectionPool connectionPool;// 连接池
    Dns dns;
    boolean followSslRedirects; // 安全套接层重定向
    boolean followRedirects;// 本地重定向
    boolean retryOnConnectionFailure;// 重试连接失败
    int callTimeout;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    int pingInterval;

    // 这里是默认配置的构建参数
    public Builder() {
        dispatcher = new Dispatcher();
        protocols = DEFAULT_PROTOCOLS;
        connectionSpecs = DEFAULT_CONNECTION_SPECS;
        ...
    }

    // 这里传入自己配置的构建参数
    Builder(OkHttpClient okHttpClient) {
        this.dispatcher = okHttpClient.dispatcher;
        this.proxy = okHttpClient.proxy;
        this.protocols = okHttpClient.protocols;
        this.connectionSpecs = okHttpClient.connectionSpecs;
        this.interceptors.addAll(okHttpClient.interceptors);
        this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
        ...
    }

2.3 同步请求

同步请求使用的是execute()方法,使用方式如下。

Response response = client.newCall(request).execute();

下面是涉及到的一些源码。

/**
* Prepares the {@code request} to be executed at   some point in the future.
*/
@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

// RealCall为真正的请求执行者
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}

@Override public Response execute() throws IOException {
    synchronized (this) {
        // 每个Call只能执行一次
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
        // 通知dispatcher已经进入执行状态
        client.dispatcher().executed(this);
        // 通过一系列的拦截器请求处理和响应处理得到最终的返回结果
        Response result = getResponseWithInterceptorChain();
        if (result == null) throw new IOException("Canceled");
        return result;
    } catch (IOException e) {
        e = timeoutExit(e);
        eventListener.callFailed(this, e);
        throw e;
    } finally {
        // 通知 dispatcher 自己已经执行完毕
        client.dispatcher().finished(this);
    }
}

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    // 在配置 OkHttpClient 时设置的 interceptors;
    interceptors.addAll(client.interceptors());
    // 负责失败重试以及重定向
    interceptors.add(retryAndFollowUpInterceptor);
    // 请求时,对必要的Header进行一些添加,接收响应时,移除必要的Header
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // 负责读取缓存直接返回、更新缓存
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // 负责和服务器建立连接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        // 配置 OkHttpClient 时设置的 networkInterceptors
        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);
}

// StreamAllocation 对象,它相当于一个管理类,维护了服务器连接、并发流
// 和请求之间的关系,该类还会初始化一个 Socket 连接对象,获取输入/输出流对象。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
  RealConnection connection) throws IOException {
    ...

    // Call the next interceptor in the chain.
    // 实例化下一个拦截器对应的RealIterceptorChain对象
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    // 得到当前的拦截器
    Interceptor interceptor = interceptors.get(index);
    // 调用当前拦截器的intercept()方法,并将下一个拦截器的RealIterceptorChain对象传递下去,最后得到响应
    Response response = interceptor.intercept(next);

    ...

    return response;
}

2.4 异步请求

异步请求使用的是enqueue(),根据异步的写法,我们可以在Builder后面跟很多的参数,如下所示。

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

client.newCall(request).enqueue(new Callback() {
    @Override 
    public void onFailure(Call call, IOException e) {
      e.printStackTrace();
    }

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

void enqueue(AsyncCall call) {
    synchronized (this) {
        readyAsyncCalls.add(call);
    }
    promoteAndExecute();
}

// 正在准备中的异步请求队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

// 运行中的异步请求
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

// 同步请求
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

// Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
// them on the executor service. Must not be called with synchronization because executing calls
// can call into user code.
private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        // 如果其中的runningAsynCalls不满,且call占用的host小于最大数量,则将call加入到runningAsyncCalls中执行,
        // 同时利用线程池执行call;否者将call加入到readyAsyncCalls中。
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
}

2.5 CacheInterceptor网络请求缓存处理

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

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

2.6 ConnectInterceptor之连接池

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<RealConnection> 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;
          }
        }

        // maxIdleConnections 表示最大允许的闲置的连接的数量,keepAliveDurationNs表示连接允许存活的最长的时间。
        // 默认空闲连接最大数目为5个,keepalive 时间最长为5分钟。
        if (longestIdleDurationNs >= this.keepAliveDurationNs
            || idleConnectionCount > this.maxIdleConnections) {
          // We've found a connection to evict. Remove it from the list, then close it     below (outside
          // of the synchronized block).
          // 该连接的时长超出了最大的活跃时长或者闲置的连接数量超出了最大允许的范围,直接移除
          connections.remove(longestIdleConnection);
        } else if (idleConnectionCount > 0) {
          // A connection will be ready to evict soon.
          // 闲置的连接的数量大于0,停顿指定的时间(等会儿会将其清理掉,现在还不是时候)
          return keepAliveDurationNs - longestIdleDurationNs;
        } else if (inUseConnectionCount > 0) {
          // All connections are in use. It'll be at least the keep alive duration 'til we run again.
          // 所有的连接都在使用中,5分钟后再清理
          return keepAliveDurationNs;
        } else {
          // No connections, idle or in use.
           // 没有连接
          cleanupRunning = false;
          return -1;
      }
}

从以上的源码分析可知,首先会对缓存中的连接进行遍历,以寻找一个闲置时间最长的连接,然后根据该连接的闲置时长和最大允许的连接数量等参数来决定是否应该清理该连接。同时注意上面的方法的返回值是一个时间,如果闲置时间最长的连接仍然需要一段时间才能被清理的时候,会返回这段时间的时间差,然后会在这段时间之后再次对连接池进行清理。

3,Retrofit

Retrofit 是一个 RESTful HTTP 网络请求框架的封装,本质上网络请求是 OkHttp 完成的,而 Retrofit 仅负责网络请求接口的封装。客户端使用Retrofit ,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作,当服务端返回数据之后,OkHttp 再将原始的结果交给 Retrofit,Retrofit然后根据用户的需求,对结果进行解析。

3.1 基本使用

首先,定义一个HTTP API,用于描述请求,比如下面是一个Get请求。

public interface GitHubService {

     @GET("users/{user}/repos")
     Call<List<Repo>> listRepos(@Path("user") String user);
}

然后,创建一个Retrofit并生成API的实现,返回类型是请求的返回值类型,方法的参数即是请求的参数。

// 1.Retrofit构建过程
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();

// 2.创建网络请求接口类实例过程
GitHubService service = retrofit.create(GitHubService.class);

最后,再调用API方法生成Call完成请求。

// 3.生成并执行请求过程
Call<List<Repo>> repos = service.listRepos("octocat");
repos.execute() or repos.enqueue()

上面是一个简单的Get请求的事例,POST请求只需要将API定义改为POST即可。Retrofit的基本使用流程很简洁,但是简洁并不代表简单,Retrofit为了实现这种简洁的使用流程,内部使用了优秀的架构设计和大量的设计模式,仔细阅读Retrofit最新版的源码会发现用到大量的设计模式。比如,Retrofit构建过程 会用到建造者模式、工厂方法模式,建网络请求接口实例过程会用到外观模式、代理模式、单例模式、策略模式、装饰模式(建造者模式),生成并执行请求过程 适配器模式(代理模式、装饰模式)。

3.2 源码分析

3.2.1 Retrofit构建过程

1,Retrofit核心对象解析

首先Retrofit中有一个全局变量非常关键,在V2.5之前的版本,使用的是LinkedHashMap(),它是一个网络请求配置对象,是由网络请求接口中方法注解进行解析后得到的。

public final class Retrofit {
    private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
   ...
}

Retrofit使用了建造者模式通过内部类Builder类建立一个Retrofit实例,如下所示。

public static final class Builder {

    // 平台类型对象(Platform -> Android)
    private final Platform platform;
    // 网络请求工厂,默认使用OkHttpCall(工厂方法模式)
    private @Nullable okhttp3.Call.Factory callFactory;
    // 网络请求的url地址
    private @Nullable HttpUrl baseUrl;
    // 数据转换器工厂的集合
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    // 网络请求适配器工厂的集合,默认是ExecutorCallAdapterFactory
    private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
    // 回调方法执行器,在 Android 上默认是封装了 handler 的 MainThreadExecutor, 默认作用是:切换线程(子线程 -> 主线程)
    private @Nullable Executor callbackExecutor;

    private boolean validateEagerly;
2,Builder内部构造
public static final class Builder {

    ...

    Builder(Platform platform) {
        this.platform = platform;
    }

    public Builder() {
        this(Platform.get());
    }

    ...

}

class Platform {

    private static final Platform PLATFORM = findPlatform();

    static Platform get() {
      return PLATFORM;
    }

    private static Platform findPlatform() {
      try {
        // 使用JVM加载类的方式判断是否是Android平台
        Class.forName("android.os.Build");
        if (Build.VERSION.SDK_INT != 0) {
          return new Android();
        }
      } catch (ClassNotFoundException ignored) {
      }
      try {
        // 同时支持Java平台
        Class.forName("java.util.Optional");
        return new Java8();
      } catch (ClassNotFoundException ignored) {
      }
      return new Platform();
    }

static class Android extends Platform {

    ...

    @Override public Executor defaultCallbackExecutor() {
        //切换线程(子线程 -> 主线程)
        return new MainThreadExecutor();
    }

    // 创建默认的网络请求适配器工厂,如果是Android7.0或Java8上,则使
    // 用了并发包中的CompletableFuture保证了回调的同步
    // 在Retrofit中提供了四种CallAdapterFactory(策略模式):
    // ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、
    // va8CallAdapterFactory、RxJavaCallAdapterFactory
    @Override List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
        @Nullable Executor callbackExecutor) {
      if (callbackExecutor == null) throw new AssertionError();
      ExecutorCallAdapterFactory executorFactory = new   ExecutorCallAdapterFactory(callbackExecutor);
      return Build.VERSION.SDK_INT >= 24
        ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
        : singletonList(executorFactory);
    }

    ...

    @Override List<? extends Converter.Factory> defaultConverterFactories() {
      return Build.VERSION.SDK_INT >= 24
          ? singletonList(OptionalConverterFactory.INSTANCE)
          : Collections.<Converter.Factory>emptyList();
    }

    ...

    static class MainThreadExecutor implements Executor {

        // 获取Android 主线程的Handler 
        private final Handler handler = new Handler(Looper.getMainLooper());

        @Override public void execute(Runnable r) {

            // 在UI线程对网络请求返回数据处理
            handler.post(r);
        }
    }
}
3, 添加baseUrl

baseUrl最基本的功能就是将String类型的url转换为OkHttp的HttpUrl的过程,涉及的代码如下。

public Builder baseUrl(String baseUrl) {
    checkNotNull(baseUrl, "baseUrl == null");
    return baseUrl(HttpUrl.get(baseUrl));
}

public Builder baseUrl(HttpUrl baseUrl) {
    checkNotNull(baseUrl, "baseUrl == null");
    List<String> pathSegments = baseUrl.pathSegments();
    if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
      throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
    }
    this.baseUrl = baseUrl;
    return this;
}
4,build过程

build()主要任务是执行Retrofit对象的创建,涉及代码如下。

public Retrofit build() {

    if (baseUrl == null) {
      throw new IllegalStateException("Base URL required.");
    }

    okhttp3.Call.Factory callFactory = this.callFactory;
    if (callFactory == null) {
        // 默认使用okhttp
         callFactory = new OkHttpClient();
    }

    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
        // Android默认的callbackExecutor
        callbackExecutor = platform.defaultCallbackExecutor();
    }

    // Make a defensive copy of the adapters and add the defaultCall adapter.
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    // 添加默认适配器工厂在集合尾部
    callAdapterFactories.addAll(platform.defaultCallAdapterFactorisca  llbackExecutor));

    // Make a defensive copy of the converters.
    List<Converter.Factory> converterFactories = new ArrayList<>(
        1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
    // Add the built-in converter factory first. This prevents overriding its behavior but also
    // ensures correct behavior when using converters thatconsumeall types.
    converterFactories.add(new BuiltInConverters());
    converterFactories.addAll(this.converterFactories);
    converterFactories.addAll(platform.defaultConverterFactories();

    return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
        unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);

}

3.2.2 创建网络请求接口实例

Retrofit.create()使用了外观模式和代理模式创建了网络请求的接口实例,创建create()的方法如下。

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
        // 判断是否需要提前缓存ServiceMethod对象
        eagerlyValidateMethods(service);
    }

    // 使用动态代理拿到请求接口所有注解配置后,创建网络请求接口实例
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new  Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          private final Object[] emptyArgs = new Object[0];

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
    });
 }

private void eagerlyValidateMethods(Class<?> service) {

  Platform platform = Platform.get();
  for (Method method : service.getDeclaredMethods()) {
    if (!platform.isDefaultMethod(method)) {
      loadServiceMethod(method);
    }
  }
}

然后,我们再看一下loadServiceMethod()方法。

ServiceMethod<?> loadServiceMethod(Method method) {

    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
            // 解析注解配置得到了ServiceMethod
            result = ServiceMethod.parseAnnotations(this, method);
            // 可以看到,最终加入到ConcurrentHashMap缓存中
            serviceMethodCache.put(method, result);
      }
    }
    return result;
}

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method   method) {
        // 通过RequestFactory解析注解配置(工厂模式、内部使用了建造者模式)
        RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

        Type returnType = method.getGenericReturnType();
        if (Utils.hasUnresolvableType(returnType)) {
          throw methodError(method,
              "Method return type must not include a type variable or wildcard: %s", returnType);
        }
        if (returnType == void.class) {
          throw methodError(method, "Service methods cannot return void.");
        }

        // 最终是通过HttpServiceMethod构建的请求方法
        return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
    }

    abstract T invoke(Object[] args);
}

3.3 Retrofit流程图

Retrofit最新的版本是2.9.0,已经大半年没有更新了。Retrofit虽然只是一个RESTful 的HTTP 网络请求框架的封装库。但是,它内部通过 大量的设计模式 封装了 OkHttp,让使用者感到它非常简洁、易懂。它内部主要是用动态代理的方式,动态将网络请求接口的注解解析成HTTP请求,最后执行请求的过程。Retrofit完整的流程如下图所示。

Android 面试 | 开源库面试题_服务端_03

4,Glide

4.1 基本使用

作为一个Android图片加载框架,Glide具有功能全、性能高,使用简单等优点。使用下面这一行代码就可以完成图片的加载与展示。

Glide.with(context).load(url).into(iv);

除此之外,我们还可以在图片的加载过程中指定一个占位图。

Glide.with(this)
     .load(url)  
     .placeholder(R.drawable.noimage) 
     .into(iv);

4.2 源码分析

下面是一个完整的Glide框架的架构图。

Android 面试 | 开源库面试题_服务端_04

4.2.1 with(context)

我们使用Glide,都是从Glide.with()方法开始的,源码如下。

public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }

    public static RequestManager with(Activity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }

    public static RequestManager with(FragmentActivity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }

    public static RequestManager with(android.app.Fragment fragment) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(fragment);
    }

    public static RequestManager with(Fragment fragment) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(fragment);
    }

可以看到,with()方法有很多,但内容基本一致,都是通过RequestManagerRetriever.get()获取RequestManagerRetriever对象retriever,然后通过retriever.get(context)获取一个RequestManager对象并返回。这些with()方法关键的不同在于传入的参数不同,可以是Context、Activity、Fragment等等。

之所以Glide在加载图片的时候要绑定with(context)方法中传入的context的生命周期,如果传入的是Activity,那么在这个Activity销毁的时候Glide会停止图片的加载。这样做的好处在于:避免了消耗多余的资源,也避免了在Activity销毁之后加载图片从而导致空指针问题。

接下来,我们看一下RequestManagerRetriever类的源码。

public class RequestManagerRetriever implements Handler.Callback {
    //饿汉式创建单例
    private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();

    //返回单例对象
    public static RequestManagerRetriever get() {
        return INSTANCE;
    }

    //根据传入的参数,获取不同的RequestManager
    public RequestManager get(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("You cannot start a load on a null Context");
        } else if (Util.isOnMainThread() && !(context instanceof Application)) {
            if (context instanceof FragmentActivity) {
                return get((FragmentActivity) context);
            } else if (context instanceof Activity) {
                return get((Activity) context);
            } else if (context instanceof ContextWrapper) {
                return get(((ContextWrapper) context).getBaseContext());
            }
        }

        return getApplicationManager(context);
    }

    //省略无关代码......
}

上面的代码是一个饿汉式单例模式,核心是根据传入的参数获取不同的RequestManager。接下来,我们看一下getApplicationManager方法。

private RequestManager getApplicationManager(Context context) {
        // 返回一个单例
        if (applicationManager == null) {
            synchronized (this) {
                if (applicationManager == null) {
                    applicationManager = new RequestManager(context.getApplicationContext(),
                            new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
                }
            }
        }
        return applicationManager;
    }

getApplicationManager(Context context)通过双检查单例模式创建并返回applicationManager。如果传入的context是Activity时,操作如下。

public RequestManager get(Activity activity) {
        //如果不在主线程或者Android SDK的版本低于HONEYCOMB,传入的还是Application类型的context
        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return get(activity.getApplicationContext());
        } else {
            //判断当前activity是否被销毁
            assertNotDestroyed(activity);
            android.app.FragmentManager fm = activity.getFragmentManager();
           //通过fragmentGet(activity, fm)获取RequestManager
            return fragmentGet(activity, fm);
        }
    }

如果传入的context是Activity时,操作如下。

public RequestManager get(Activity activity) {
        //如果不在主线程或者Android SDK的版本低于HONEYCOMB,传入的还是Application类型的context
        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return get(activity.getApplicationContext());
        } else {
            //判断当前activity是否被销毁
            assertNotDestroyed(activity);
            android.app.FragmentManager fm = activity.getFragmentManager();
           //通过fragmentGet(activity, fm)获取RequestManager
            return fragmentGet(activity, fm);
        }
    }

下面是with()方法的一个简单的总结。

  • 通过RequestManagerRetriever.get()获取RequestManagerRetriever单例对象。
  • 通过retriever.get(context)获取RequestManager,在get(context)方法中通过对context类型的判断做不同的处理。
  • 当context是Application,通过getApplicationManager(Context context)创建并返回一个RequestManager对象。
  • 当context是Activity,通过fragmentGet(activity, fm)在当前activity创建并添加一个没有界面的fragment,从而实现图片加载与activity的生命周期相绑定,之后创建并返回一个RequestManager对象。

4.2.2 load(url)

load(url)主要是用于加载网络的图片,如下所示。

Glide.with(context)
    .load(url)
    .placeholder(R.drawable.place_image)
    .error(R.drawable.error_image)
    .into(imageView);

load(url)方法的源码如下。

public DrawableTypeRequest<String> load(String string) {
        return (DrawableTypeRequest<String>) fromString().load(string);
}

load()方法涉及fromString()、load(string)两个方法,接下来看下这两个方法。

fromString()

public DrawableTypeRequest<String> fromString() {
        return loadGeneric(String.class);
    }

private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
        ModelLoader<T, InputStream> streamModelLoader = 
                        Glide.buildStreamModelLoader(modelClass, context);
        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
                Glide.buildFileDescriptorModelLoader(modelClass, context);
        if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
            throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
                    + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
                    + " Glide#register with a ModelLoaderFactory for your custom model class");
        }

        //这句是核心,本质是创建并返回了一个DrawableTypeRequest
        return optionsApplier.apply(
                new DrawableTypeRequest<T>(modelClass, streamModelLoader, 
                        fileDescriptorModelLoader, context,
                        glide, requestTracker, lifecycle, optionsApplier));
    }

loadGeneric()方法本质是创建并返回一个DrawableTypeRequest,Drawable类型的请求。

load(string) load(string)方法的代码如下。

@Override
 public DrawableRequestBuilder<ModelType> load(ModelType model) {
    super.load(model);
    return this;
}

此方法首先调用DrawableRequestBuilder的父类GenericRequestBuilder的load()方法,然后返回自身。接下来,看一下DrawableRequestBuilder父类中的load()方法,如下所示。

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
        this.model = model;
        isModelSet = true;
        return this;
    }

DrawableRequestBuilder的父类是GenericRequestBuilder,从名字中我们也可以看出来,前者是Drawable请求的构建者,后者是通用的请求构建者,他们是子父关系。

经过上面的分析我们可以知道,在Glide.with(context).load(url)之后会返回一个DrawableTypeRequest的对象,它的父类是DrawableRequestBuilder,DrawableRequestBuilder的父类是GenericRequestBuilder,我们写的placeHolder()、error()等等相关图片请求配置的方法都定义在GenericRequestBuilder中。

4.2.3 into(imageView)

Glide中的前两部是创建了一个Request,这个Request可以理解为对图片加载的配置请求,需要注意的是仅仅是创建了一个请求,而没有去执行。只有当调用into()方法时,这个请求才会真正的被执行。into(imageView)的源码如下。

public Target<GlideDrawable> into(ImageView view) {
        return super.into(view);
    }

发现它调用的是父类GenericRequestBuilder的into()方法,那我们继续看GenericRequestBuilder中的into()方法。

public Target<TranscodeType> into(ImageView view) {
        //确保在主线程
        Util.assertMainThread();
        //确保view不为空
        if (view == null) {
            throw new IllegalArgumentException("You must pass in a non null View");
        }
        //对ScaleType进行配置
        if (!isTransformationSet && view.getScaleType() != null) {
            switch (view.getScaleType()) {
                case CENTER_CROP:
                    applyCenterCrop();
                    break;
                case FIT_CENTER:
                case FIT_START:
                case FIT_END:
                    applyFitCenter();
                    break;
                //$CASES-OMITTED$
                default:
                    // Do nothing.
            }
        }

        //核心
        return into(glide.buildImageViewTarget(view, transcodeClass));
    }

可以看到,上面的方法就是into()的核心代码,它定义在GenericRequestBuilder这个通用的请求构建者中。方法的核心是最后一行:into(glide.buildImageViewTarget(view, transcodeClass)),首先通过glide.buildImageViewTarget(view, transcodeClass)创建出一个Target类型的对象,然后把这个target传入GenericRequestBuilder中的into()方法中。

关于buildImageViewTarget(view, transcodeClass)方法,我们就不再赘述了。

6,EventBus

6.1 基本使用

6.1.1 基本概念

EventBus是一种用于Android的事件发布-订阅的事件总线。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。

EventBus由三个角色构成:Publisher、Event和Subscriber。

  • Event:事件,它可以是任意类型,EventBus会根据事件类型进行全局的通知。
  • Subscriber:事件订阅者,在EventBus 3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe,并且指定线程模型,默认是POSTING。
  • Publisher:事件的发布者,可以在任意线程里发布事件。一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。

EventBus是一种典型的事件发布-订阅模式,事件由发布者通过EvenentBus传递给订阅者,总体框架如下。 [图片上传失败…(image-91c9a-1659682506847)]

EventBus提供了四种线程模型,分别是:

  • POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程。
  • MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。
  • BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。
  • ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。

6.1.2 基本使用

EventBus使用流程上分为3步。首先,定义一个事件类。

public class SayHelloEvent {
    private String message;

    public void sayHellow(String message) {
        this.message = message;
    }
}

然后,准备一个事件的订阅者,为了防止事件带来的性能消耗问题,还需要在onStop生命周期中注销事件的订阅。

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(SayHelloEvent event) {
    String message = event.getMessage();
    ...
}

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}

最后,在需要发送事件的地方调用EventBus.getDefault().post方法发送事件。

EventBus.getDefault().post(new SayHelloEvent("Hello,EventBus!!!"));
复制代码

6.2 源码分析

6.2.1 EventBus.getDefault().register(this)

首先,我们从获取EventBus实例的方法getDefault()开发看EventBus。

public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

可以看到,在getDefault()中使用了双重校验并加锁的单例模式来创建EventBus实例,然后我们看一下构造方法。

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

public EventBus() {
    this(DEFAULT_BUILDER);
}

在EventBus的默认构造方法中又调用了它的另一个有参构造方法,将一个类型为EventBusBuilder的DEFAULT_BUILDER对象传递进去了。再看一下EventBusBuilder的构造方法。

public class EventBusBuilder {
    ...

    EventBusBuilder() {
    }    
    ...

}

里面基本什么也没做,继续查看EventBus的这个有参构造方法。

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;

EventBus(EventBusBuilder builder) {
    ...

    // 1
    subscriptionsByEventType = new HashMap<>();

    // 2
    typesBySubscriber = new HashMap<>();

    // 3
    stickyEvents = new ConcurrentHashMap<>();

    // 4
    mainThreadSupport = builder.getMainThreadSupport();
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);

    ...

    // 5
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);

    // 从builder取中一些列订阅相关信息进行赋值
    ...

    // 6
    executorService = builder.executorService;
}

在注释1处,创建了一个subscriptionsByEventType对象,可以看到它是一个类型为HashMap的subscriptionsByEventType对象,并且其key为 Event 类型,value为 Subscription链表。这里的Subscription是一个订阅信息对象,它里面保存了两个重要的字段,一个是类型为 Object 的 subscriber,该字段即为注册的对象(在 Android 中时通常是 Activity对象);另一个是 类型为SubscriberMethod 的 subscriberMethod,它就是被@Subscribe注解的那个订阅方法,里面保存了一个重要的字段eventType,它是 Class<?> 类型的,代表了 Event 的类型。在注释2处,新建了一个类型为 Map 的typesBySubscriber对象,它的key为subscriber对象,value为subscriber对象中所有的 Event 类型链表,日常使用中仅用于判断某个对象是否注册过。在注释3处新建了一个类型为ConcurrentHashMap的stickyEvents对象,它是专用于粘性事件处理的一个字段,key为事件的Class对象,value为当前的事件。

可能有的同学还不知道粘性事件,所谓粘性事件是相对普通事件来说的。普通事件是先注册,然后发送事件才能收到;而粘性事件,在发送事件之后再订阅该事件也能收到。并且,粘性事件会保存在内存中,每次进入都会去内存中查找获取最新的粘性事件,除非你手动解除注册。
然后,我们看注释5这行代码新建了一个subscriberMethodFinder对象,这是从EventBus中抽离出的订阅方法查询的一个对象。在注释6处,从builder中取出了一个默认的线程池对象,它由Executors的newCachedThreadPool()方法创建,它是一个有则用、无则创建、无数量上限的线程池。
接下来,我们再看一下EventBus的regist()方法。

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    
    // 1
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // 2
            subscribe(subscriber, subscriberMethod);
        }
    }
}

上面代码的主要作用是,根据当前注册类获取 subscriberMethods这个订阅方法列表,然后使用了增强for循环令subsciber对象 对 subscriberMethods 中每个 SubscriberMethod 进行订阅。
更深层次的代码,我们就不做分析了,regist()主要完成了如下一些事情。

根据单例设计模式创建一个EventBus对象,同时创建一个EventBus.Builder对象对EventBus进行初始化,其中有三个比较重要的集合和一个SubscriberMethodFinder对象。
调用register方法,首先通过反射获取到订阅者的Class对象。
通过SubscriberMethodFinder对象获取订阅者中所有订阅的事件集合,它先从缓存中获取,如果缓存中有,直接返回;如果缓存中没有,通过反射的方式去遍历订阅者内部被注解的方法,将这些方法放入到集合中进行返回。
遍历第三步获取的集合,将订阅者和事件进行绑定。
在绑定之后会判断绑定的事件是否是粘性事件,如果是粘性事件,直接调用postToSubscription方法,将之前发送的粘性事件发送给订阅者。其实这也很好理解,在讲粘性事件时说过,如果在粘性事件发送之前注册的订阅者,当发送粘性事件时,会接收到该事件;如果是粘性事件发送之后注册的订阅者,同样也能接收到事件。

6.2.2 EventBus.getDefault().post()
post()方法的代码如下。

public void post(Object event) {
 
    PostingThreadState postingState = currentPostingThreadState.get();
    List <Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);
   
    if (!postingState.isPosting) {
        postingState.isMainThread = isMainThread();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

此方法的作用是,获取当前线程的事件集合,将要发送的事件加入到集合中。然后通过循环,只要事件集合中还有事件,就一直发送。这里的currentPostingThreadState 是一个 ThreadLocal 类型的对象,里面存储了 PostingThreadState,而 PostingThreadState 中包含了一个 eventQueue 和其他一些标志位,相关的源码如下。

private final ThreadLocal <PostingThreadState> currentPostingThreadState = new ThreadLocal <PostingThreadState> () {
@Override
protected PostingThreadState initialValue() {
    return new PostingThreadState();
}
};

final static class PostingThreadState {
    final List <Object> eventQueue = new ArrayList<>();
    boolean isPosting;
    boolean isMainThread;
    Subscription subscription;
    Object event;
    boolean canceled;
}

然后,我们看一下postSingleEvent() 方法,代码如下。

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
  
    if (eventInheritance) {
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            subscriptionFound |=
            postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    if (!subscriptionFound) {
        ...
    }
}

上面代码的作用是,获取事件的Class对象,找到当前的event的所有父类和实现的接口的class集合。遍历这个集合,调用发送单个事件的方法进行发送。
可以看到,上面的代码首先取出 Event 的 class 类型,接着会对 eventInheritance 标志位 判断,它默认为true,如果设为 true 的话,它会在发射事件的时候判断是否需要发射父类事件,设为 false,能够提高一些性能。接着,调用lookupAllEventTypes() 方法,它的作用就是取出 Event 及其父类和接口的 class 列表,当然重复取的话会影响性能,所以它也做了一个 eventTypesCache 的缓存,这样就不用重复调用 getSuperclass() 方法。最后,调用postSingleEventForEventType()方法。

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class <?> eventClass) {
    CopyOnWriteArrayList <Subscription> subscriptions;
    synchronized(this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription: subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

根据事件获取所有订阅它的订阅者集合,遍历集合,将事件发送给订阅者。方法最后又调用了postToSubscription()方法。

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case MAIN_ORDERED:
            if (mainThreadPoster != null) {
                mainThreadPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case BACKGROUND:
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknow thread mode: " + subscription.subscriberMethod.threadMode);
    }
}

可以看出,通过threadMode 来判断在哪个线程中去执行订阅消息。

POSTING:执行 invokeSubscriber() 方法,内部直接采用反射调用。
MAIN:首先去判断当前是否在 UI 线程,如果是的话则直接反射调用,否则调用mainThreadPoster的enqueue()方法,即把当前的方法加入到队列之中,然后通过 handler 去发送一个消息,在 handler 的 handleMessage 中去执行方法。
MAIN_ORDERED:与MAIN类似,不过是确保是顺序执行的。
BACKGROUND:判断当前是否在 UI 线程,如果不是的话则直接反射调用,是的话通过backgroundPoster的enqueue()方法 将方法加入到后台的一个队列,最后通过线程池去执行。注意,backgroundPoster在 Executor的execute()方法 上添加了 synchronized关键字 并设立 了控制标记flag,保证任一时间只且仅能有一个任务会被线程池执行。
ASYNC:逻辑实现类似于BACKGROUND,将任务加入到后台的一个队列,最终由Eventbus 中的一个线程池去调用,这里的线程池与 BACKGROUND 逻辑中的线程池用的是同一个,即使用Executors的newCachedThreadPool()方法创建的线程池,它是一个有则用、无则创建、无数量上限的线程池。不同于backgroundPoster的保证任一时间只且仅能有一个任务会被线程池执行的特性,这里asyncPoster则是异步运行的,可以同时接收多个任务。

可以看到,EventBus.getDefault().post()主要做了如下一些事情:

获取当前线程的事件集合,将要发送的事件加入到集合中。
通过循环,只要事件集合中还有事件,就一直发送。
获取事件的Class对象,找到当前的event的所有父类和实现的接口的class集合。遍历这个集合,调用发送单个事件的方法进行发送。
根据事件获取所有订阅它的订阅者集合,遍历集合,将事件发送给订阅者。
发送给订阅者时,根据订阅方法的线程模式调用订阅方法,如果需要线程切换,则切换线程进行调用;否则,直接调用。

6.2.3 EventBus.getDefault().unregister(this)
前面已经介绍了订阅者注册和消息的发送,接下来我们再来看一下消息的解绑。

public synchronized void unregister(Object subscriber) {
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    }
}

可以看到,在unsubscribeByEventType() 方法中对 subscriptionsByEventType 移除了该 subscriber 的所有订阅信息。最后,在移除了注册对象和其对应的所有 Event 事件链表。

6.2.4 EventBus.getDefault.postSticky()
如果想要发射 sticky 事件,需要通过 EventBus的postSticky() 方法,如下所示。

public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    post(event);
}

上面代码的主要作用是先将该事件放入StickyEvents 中,然后再使用post()方法发送事件。前面我们在分析register()方法的时候,有一段粘性事件的分析。

if (subscriberMethod.sticky) {
    Object stickyEvent = stickyEvents.get(eventType);
    if (stickyEvent != null) {
        postToSubscription(newSubscription, stickyEvent, isMainThread());
    }
}

可以看到,在注册事件时,熟悉会判断当前事件是否是 sticky 事件,如果是则从 stickyEvents 中拿出该事件并执行 postToSubscription() 方法。

标签:面试题,缓存,return,get,开源,new,Android,null,public
From: https://blog.51cto.com/u_16163453/6534401

相关文章

  • Android ANR 实现机制详解
    一ANR概述ANR(ApplicationNotResponding),即应用程序无响应,Android系统指定某些事件需要在规定时间内完成,如果超过预定时间还能未能得到有效响应,就会造成ANR。具体表现为,应用位于前台时,系统会向用户显示一个对话框,如下图所示。用户可以选择“wait”让程序继续运行,也可以选择“......
  • 史上最全Android性能优化方案解析
    Android中的性能优分为以下几个方面:布局优化网络优化安装包优化内存优化卡顿优化启动优化……一.布局优化布局优化的本质就是减少View的层级。常见的布局优化方案如下:在LinearLayout和RelativeLayout都可以完成布局的情况下优先选择LinearLayout,可以减少View的层级,但是注意相同组......
  • Android App运行核心,Handler,Looper,Message
    目标在手机屏幕上显示指定的区域两种方法在已存在的图片上,绘制矩形,查看图片在全屏透明悬浮窗上直接画矩形已有图片绘制矩形读取图片letimgFilepath=files.path("./chess.png");letimg=images.read(imgFilepath);设置绘制区域letrect={left:52,top:20,rig......
  • android RecyclerView嵌套 RecyclerView 子item 和 父item点击事件如何处理
    前言经常会遇到列表嵌套列表的场景,那么父item和子item会有点击重叠该怎么处理尼?先上效果父adapteropenclassStoreListAdapter(layoutResId:Int,data:MutableList<StoreGoodsBean>):BaseQuickAdapter<StoreGoodsBean,BaseViewHolder>(layoutResId,data){//子adapt......
  • Android开发必备——注解
    前言阅读官方源码以及各类第三方框架时可以发现,很多地方都有注解,作为一名Android程序员,掌握注解属于必不可少的一项技能。1.什么是注解注解是以@符号开头的用来标识如类、字段、方法等的工具。说到注解,就不得不提另外一个概念——注释,两者其实都是做解释的功能,只不过注释是面向开......
  • Android 屏幕适配基础
    Pixels和dp、sp的区别不同屏幕密度下,1p显示的物理长度不同1dp在不同屏幕上显示相同的物理长度sp只用在字体上,和dp一样为了让在不同设备上有一致的显示效果单位尺寸搞清楚屏幕的各种单位含义,是屏幕适配的基础屏幕尺寸含义:手机对角线的物理尺寸单位:英寸(inch),1英寸=2.54cm屏幕尺寸......
  • Android 多任务配置
    Android多任务配置本篇文章是为了了解安卓中的多任务系统,了解安卓中Task的栈结构,以及怎么配置app的多任务,模仿微信小程序。多任务配置在安卓手机上,当我们打开微信小程序,可以看到小程序其实是一个独立的任务,这是怎么配置的呢。其实很简单,我们只需要在安卓项目的配置文件中,对Activit......
  • 好家伙66万字,又一份牛逼的Android笔记面世了
    前言很久以前,凭借四大组件、Java基础等知识,便可开开心心的开发,轻松地上岗。而随着Android的不断发展完善,各种组件库越来越成熟,学习资料越来越多,我们却慢慢地看不到方向。信息爆炸的时代,到底该如何提升自己?如何才能找到真正利己的信息?如何才能看清前路的方向?为此我专门找了一份《And......
  • 四年Android开发,在拉勾上投了十几个简历,没有一个面试邀请......药丸了
    在浏览某论坛的时候看到一名程序员吐槽:坐标杭州,四年Android开发一枚,技术不顶尖也不算差吧,这边加班太猛了,在考虑换一个岗位。在拉勾上投了十几个简历,全都是不合适,没有一个面试邀请!!!简历在拉勾上是开放的,竟然没有一个感兴趣的公司打电话给我。前年这个时候,接到的电话还是很多的,这才过......
  • Android视图加载优化——Factory2设置方法
    前言Factory2是直接继承于Factory,继续跟踪下Factory的源码,比Factory的功能更加强大。当我们新建Activity的时候,大部分情况是继承AppCompatActivity。提供了向后兼容性。本文将深入探索AppCompatActivity的视图加载,探索将xml布局文件中的TextView替换成AppCompatTextVi......