Android 面試之開源庫分析

有人說,如今的客戶端面試愈來愈捲了,須要開發者掌握的內容也愈來愈多,從基礎的Java基礎、Android基礎、Android系統原理、Android第三方庫、混合開發、數據結構、算法,無一不問,要想獲得一份好的工做機會,確實是這樣的。下面是我給你們總結的Android面試題。java

1,Android 面試之必問Java基礎
2,Android 面試之必問Android基礎
3,Android 面試之必問高級知識點
4,Android 面試之必問性能優化android

1,HTTP與緩存理論

1.1 HTTP緩存策略

HTTP的緩存機制也是依賴於請求和響應header裏的參數類實現的,最終的響應結果是從緩存仍是從服務端拉取是有一套完整的機制的,HTTP的緩存機制的流程以下所示。
在這裏插入圖片描述
HTTP的緩存能夠分爲兩種:強制緩存和對比緩存git

1.2 強制緩存

要服務端參與判斷是否繼續使用緩存,當客戶端第一次請求數據是,服務端返回了緩存的過時時間(Expires與Cache-Control),沒有過時就能夠繼續使用緩存,不然則不適用,無需再向服務端詢問。github

強制緩存使用的的兩個標識:web

  • 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常見的一些狀態碼:api

  • 100~199:指示信息,表示請求已接收,繼續處理。
  • 200~299:請求成功,表示請求已被成功接收、理解。
  • 300~399:重定向,要完成請求必須進行更進一步的操做。
  • 400~499:客戶端錯誤,請求有語法錯誤或請求沒法實現。
  • 500~599:服務器端錯誤,服務器未能實現合法的請求。

2.1 OKHttp請求流程

下面是OKHttp內部發起請求的大體流程圖,以下圖。
在這裏插入圖片描述
下面是使用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完整的流程以下圖所示。
在這裏插入圖片描述

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框架的架構圖。
在這裏插入圖片描述

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傳遞給訂閱者,整體框架以下。
在這裏插入圖片描述
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()主要完成了以下一些事情。

  1. 根據單例設計模式建立一個EventBus對象,同時建立一個EventBus.Builder對象對EventBus進行初始化,其中有三個比較重要的集合和一個SubscriberMethodFinder對象。
  2. 調用register方法,首先經過反射獲取到訂閱者的Class對象。
  3. 經過SubscriberMethodFinder對象獲取訂閱者中全部訂閱的事件集合,它先從緩存中獲取,若是緩存中有,直接返回;若是緩存中沒有,經過反射的方式去遍歷訂閱者內部被註解的方法,將這些方法放入到集合中進行返回。
  4. 遍歷第三步獲取的集合,將訂閱者和事件進行綁定。
  5. 在綁定以後會判斷綁定的事件是不是粘性事件,若是是粘性事件,直接調用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()主要作了以下一些事情:

  1. 獲取當前線程的事件集合,將要發送的事件加入到集合中。
  1. 經過循環,只要事件集合中還有事件,就一直髮送。
  2. 獲取事件的Class對象,找到當前的event的全部父類和實現的接口的class集合。遍歷這個集合,調用發送單個事件的方法進行發送。
  3. 根據事件獲取全部訂閱它的訂閱者集合,遍歷集合,將事件發送給訂閱者。
  4. 發送給訂閱者時,根據訂閱方法的線程模式調用訂閱方法,若是須要線程切換,則切換線程進行調用;不然,直接調用。

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() 方法。

相關文章
相關標籤/搜索