OkHttp源碼學(xué)習(xí)隨筆

OkHttp是什么?

簡介

OkHttp是一款優(yōu)秀的HTTP框架,它支持get請求和post請求,支持基于Http的文件上傳和下載,支持加載圖片,支持下載文件透明的GZIP壓縮,支持響應(yīng)緩存避免重復(fù)的網(wǎng)絡(luò)請求,支持使用連接池來降低響應(yīng)延遲問題。OkHttp由Square公司開發(fā),是目前Android最熱門的網(wǎng)絡(luò)框架之一。

官網(wǎng)網(wǎng)址:OKHttp官網(wǎng)

Github地址:Github

特點

  1. 支持HTTP2/SPDY
  2. socket自動選擇最好路線,并支持自動重連
  3. 擁有自動維護(hù)的socket連接池,減少握手次數(shù)
  4. 擁有隊列線程池,輕松寫并發(fā)
  5. 擁有Interceptors輕松處理請求與響應(yīng)(比如透明GZIP壓縮)基于Headers的緩存策略

OkHttp怎么用?

1、gradle引入庫,implementation 'com.squareup.okhttp3:okhttp:3.11.0'

2、初始化OkHttpClient對象

 client = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(15, TimeUnit.SECONDS)
                .writeTimeout(15, TimeUnit.SECONDS)
                .build();

同步請求

public void okHttpSync() {
        Request request = new Request.Builder()
                .url("https://www.baidu.com")
                .build();
        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            if (response.isSuccessful()) {
                System.out.println("response.code()==" + response.code());
                System.out.println("response.heard()==" + response.headers());
                System.out.println("response.message()==" + response.message());
                System.out.println("res==" + response.body().string());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

異步請求

    public void okHttpAsync() {
        Request request = new Request.Builder()
                .url("https://www.baidu.com")
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                needCancelled.set(true);
                System.out.println("url==" + call.request().url());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    System.out.println("response.code()==" + response.code());
                    System.out.println("response.heard()==" + response.headers());
                    System.out.println("response.message()==" + response.message());
                    System.out.println("res==" + response.body().string());
                    needCancelled.set(true);
                }
            }
        });
    }

詳細(xì)的OkHttp使用可參考OKHttp使用詳解

OkHttp核心執(zhí)行流程是怎樣?

關(guān)鍵類功能說明

功能說明
OKHttpClient 里面包含了很多對象,OKhttp的很多功能模塊都包裝進(jìn)這個類,讓這個類單獨提供對外的API,使用Builder模型構(gòu)建
Request、Response 抽象的網(wǎng)絡(luò)輸入及響應(yīng)模型
Call HTTP請求任務(wù)封裝,是一個接口
RealCall Call的實現(xiàn),實現(xiàn)execute()同步方法、enqueue(Callback responseCallback)異步方法, getResponseWithInterceptorChain() 獲取攔截器響應(yīng)
AsyncCall RealCall的內(nèi)部類。繼承了Runnable接口,后續(xù)在異步的線程池中執(zhí)行
Dispatcher 核心調(diào)度類,內(nèi)部維護(hù)為了readyAsyncCalls、runningAsyncCalls、runningSyncCalls隊列,實際RealCall后續(xù)也是調(diào)用該類進(jìn)行同步、異步的具體實現(xiàn)。內(nèi)部維護(hù)了一個線程池,限制了最大并發(fā)數(shù)maxRequests=64。
RealInterceptorChain 攔截器鏈,維護(hù)了一個interceptors隊列,每次proceed通過index + 1會執(zhí)行下一攔截器的intercept方法
RetryAndFollowUpInterceptor 負(fù)責(zé)失敗重連以及重定向
BridgeInterceptor 負(fù)責(zé)對Request和Response報文進(jìn)行加工
CacheInterceptor 負(fù)責(zé)緩存攔截器
ConnectInterceptor 負(fù)責(zé)維護(hù)連接攔截器
CallServerInterceptor 負(fù)責(zé)最后網(wǎng)絡(luò)IO的讀寫

代碼執(zhí)行流程

image

1、通過Builder模式統(tǒng)一構(gòu)建OkHttpClient對象

2、通過Call,實現(xiàn)類RealCall進(jìn)行請求發(fā)送

3、RealCall通過調(diào)用了Dispatcher的execute()及enqueue()方法進(jìn)行同步及異步的請求

4、最終調(diào)用ReallCall的getResponseWithInterceptorChain()方法進(jìn)行攔截鏈的攔截

5、依次通過重定向攔截器、橋接攔截器、緩存攔截器、連接攔截器、網(wǎng)絡(luò)攔截器依次進(jìn)行處理

6、最后通過intercept的return往回返回Response,最終返回給客戶端請求的結(jié)果

OkHttp如何進(jìn)行線程調(diào)度控制?

線程調(diào)度

在Dispatcher中維護(hù)了一個線程池,異步的請求會將任務(wù)加入到線程池中。

 public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

默認(rèn)的最大并發(fā)數(shù)為maxRequests=64,如果超過限制會加入到等待隊列中,執(zhí)行異步的方法如下

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

最后線程池執(zhí)行AsyncCall中的execute()方法,如下

   @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

隊列機制

Dispathcer中維護(hù)了3個隊列,分別為異步等待隊列、異步執(zhí)行隊列、同步執(zhí)行隊列。

 /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

不管是同步還是異步,最終在finally塊都會調(diào)用dispatcher的finished方法,會移除掉該隊列任務(wù),最后實現(xiàn)如下

 int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }

在finish中會再調(diào)用promoteCalls方法,會重新檢索準(zhǔn)備中的隊列,將隊列加入到線程中

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

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

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

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

OkHttp的攔截器及調(diào)用鏈?zhǔn)窃趺磮?zhí)行?

調(diào)用鏈執(zhí)行流程

通過上述的分析,我們知道不管同步還是異步,最終調(diào)用到的都是RealCall的getResponseWithInterceptorChain()方法,如下:

 Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      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);
  }

其中定義了攔截器集合及RealInterceptorChain攔截鏈,具體執(zhí)行了攔截鏈的proceed方法,如下:

 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

1、先判斷是否超過list的size,如果超過則遍歷結(jié)束,如果沒有超過則繼續(xù)執(zhí)行

2、calls+1

3、new了一個RealInterceptorChain,其中然后下標(biāo)index+1

4、從list取出下一個interceptor對象

5、執(zhí)行interceptor的intercept方法

總結(jié)一下就是每一個RealInterceptorChain對應(yīng)一個interceptor,然后每一個interceptor再產(chǎn)生下一個RealInterceptorChain,直到List迭代完成。

攔截器

image

從上面的調(diào)用關(guān)系可以看出除了紅色圈出的攔截器之外都是系統(tǒng)提供的攔截器,這整個過程是遞歸的執(zhí)行過程,在 CallServerInterceptor 中得到最終的 Response 之后,將 response 按遞歸逐級進(jìn)行返回,期間會經(jīng)過 NetworkInterceptor 最后到達(dá) Application Interceptor 。

OkHttp是如何進(jìn)行數(shù)據(jù)緩存?

緩存策略

OkHttp使用了CacheInterceptor攔截器進(jìn)行數(shù)據(jù)緩存的控制使用了CacheStrategy實現(xiàn)了上面的流程圖,它根據(jù)之前緩存的結(jié)果與當(dāng)前將要發(fā)送Request的header進(jìn)行策略,并得出是否進(jìn)行請求的結(jié)果。根據(jù)輸出的networkRequest和cacheResponse的值是否為null給出不同的策略,如下:

networkRequest cacheResponse result 結(jié)果
null null only-if-cached (表明不進(jìn)行網(wǎng)絡(luò)請求,且緩存不存在或者過期,一定會返回503錯誤)
null non-null 不進(jìn)行網(wǎng)絡(luò)請求,直接返回緩存,不請求網(wǎng)絡(luò)
non-null null 需要進(jìn)行網(wǎng)絡(luò)請求,而且緩存不存在或者過去,直接訪問網(wǎng)絡(luò)
non-null non-null Header中包含ETag/Last-Modified標(biāo)簽,需要在滿足條件下請求,還是需要訪問網(wǎng)絡(luò)

緩存算法

通過分析CacheInterceptor攔截器的intercept方法,我們可以發(fā)現(xiàn)具體的緩存都是使用了Cache類進(jìn)行,最后具體的實現(xiàn)在DiskLruCache類中。緩存實際上是一個比較復(fù)雜的邏輯,單獨的功能塊,實際上不屬于OKhttp上的功能,實際上是通過是http協(xié)議和DiskLruCache做了處理。LinkedHashMap可以實現(xiàn)LRU算法,并且在這個case里,它被用作對DiskCache的內(nèi)存索引

有興趣可以參考如下2篇文章的具體實現(xiàn):

OKHttp源碼解析(六)--中階之緩存基礎(chǔ)

OKHttp源碼解析(七)--中階之緩存機制

OkHttp的連接池復(fù)用機制是怎么樣?

鏈路

RealConnection是Connection的實現(xiàn)類,代表著鏈接socket的鏈路,如果擁有了一個RealConnection就代表了我們已經(jīng)跟服務(wù)器有了一條通信鏈路,而且通過
RealConnection代表是連接socket鏈路,RealConnection對象意味著我們已經(jīng)跟服務(wù)端有了一條通信鏈路。
另外StreamAllocation類為流的橋梁,在RetryAndFollowUpInterceptor中進(jìn)行初始化,在ConnectInterceptor中進(jìn)行newStream操作,具體的連接攔截器代碼如下:

 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 httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

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

newStream創(chuàng)建留最后會調(diào)用到findConnection方法,這里面是連接復(fù)用的關(guān)鍵,如果再連接池中找到能復(fù)用的連接,則直接返回。
否則將RealConnection加入到鏈接池ConnectionPool中,具體代碼如下:

 private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      // 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;
      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!
        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.
        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.
    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;
  }

連接池

OkHttp中使用ConnectionPool管理http和http/2的鏈接,以便減少網(wǎng)絡(luò)請求延遲。同一個address將共享同一個connection。該類實現(xiàn)了復(fù)用連接的目標(biāo)。一個OkHttpClient只包含一個ConnectionPool,其實例化也是在OkHttpClient的過程。這里說一下ConnectionPool各個方法的調(diào)用并沒有直接對外暴露,而是通過OkHttpClient的Internal接口統(tǒng)一對外暴露。

1、獲取連接使用get方法,或獲取是否有合適的鏈接,否則返回null,具體實現(xiàn)如下:

 RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection, true);
        return connection;
      }
    }
    return null;
  }

2、加入連接使用put方法,并且會是會觸發(fā)cleanupRunnable,清理連接。具體實現(xiàn)如下:

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }

3、具體的連接回收機制,首先統(tǒng)計空閑連接數(shù)量,然后通過for循環(huán)查找最長空閑時間的連接以及對應(yīng)空閑時長,然后判斷是否超出最大空閑連接數(shù)(maxIdleConnections)或者或者超過最大空閑時間(keepAliveDurationNs),滿足其一則清除最長空閑時長的連接。如果不滿足清理條件,則返回一個對應(yīng)等待時間。具體的實現(xiàn)如下:

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

      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.
        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.
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
        cleanupRunning = false;
        return -1;
      }
    }

    closeQuietly(longestIdleConnection.socket());

    // Cleanup again immediately.
    return 0;
  }

OkHttp的底層網(wǎng)絡(luò)實現(xiàn)是什么?

1、OkHttp使用okio進(jìn)行io的操作。okio是由square公司開發(fā)的,它補充了java.io和java.nio的不足,以便能夠更加方便,快速的訪問、存儲和處理你的數(shù)據(jù)。OKHttp底層也是用該庫作為支持。而且okio使用起來很簡單,減少了很多io操作的基本代碼,并且對內(nèi)存和CPU使用做了優(yōu)化。

2、沒有依賴其他的關(guān)于Http實現(xiàn)的庫,底層使用了Socket,自己實現(xiàn)了Http1.X及2.X的協(xié)議。

OkHttp中代碼運用了那些設(shè)計模式,有什么巧妙的設(shè)計?

1、建造者模式

不管是OkHttpClient對象的創(chuàng)建還是Request對象、Respone對象,都使用了建造者模式,將復(fù)雜的對象創(chuàng)建統(tǒng)一在不同方法中,使得創(chuàng)建的過程更加簡單。

2、外觀模式
OkHttpClient對外提供了統(tǒng)一的調(diào)度,屏蔽了內(nèi)部的實現(xiàn),使得使用該網(wǎng)絡(luò)庫簡單便捷。

3、責(zé)任鏈模式
OkHttp中的攔截器使用了責(zé)任鏈模式,將不同的攔截器獨立實現(xiàn),動態(tài)組成鏈的調(diào)用形式。責(zé)任清晰,可動態(tài)擴(kuò)展。

為什么要用OkHttp?

目前Android開發(fā)中,主要的網(wǎng)絡(luò)框架有HttpClient、Volley、HttpURLConnection、OkHttp。

其中Android早就不推薦httpclient,5.0之后干脆廢棄,6.0刪除了HttpClient。所以HttpClient不考慮。Volley框架現(xiàn)在也已經(jīng)不再升級了,故目前考慮使用的有、HttpURLConnection及OkHttp。

相對HttpURLConnection,OkHttp使用更加便捷及靈活,且第三方社區(qū)活躍,相關(guān)資料齊全,成熟穩(wěn)定性高。OkHttp也得到了官方的認(rèn)可,并在不斷優(yōu)化更新,所以建議應(yīng)用優(yōu)先選擇OkHttp作為網(wǎng)絡(luò)框架。

總結(jié)

思考

在項目的開發(fā)過程中,我們經(jīng)常使用到大量的第三方框架,但可能知其然不知其所以然,通過不斷的思考反問為什么,從而去研究源碼中的實現(xiàn)。能讓我們對框架更加運用自如及解決一些底層疑難的問題。

參考資料

OKHttp使用詳解

OKHTTP結(jié)合官網(wǎng)示例分析兩種自定義攔截器的區(qū)別

OKHttp源碼解析(一)系列

關(guān)于

歡迎關(guān)注我的個人公眾號

微信搜索:一碼一浮生,或者搜索公眾號ID:life2code

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,316評論 25 708
  • 用OkHttp很久了,也看了很多人寫的源碼分析,在這里結(jié)合自己的感悟,記錄一下對OkHttp源碼理解的幾點心得。 ...
    藍(lán)灰_q閱讀 4,536評論 4 34
  • 火車一路西行,由合肥始,經(jīng)蚌埠,徐州,向西過龍門,華山直抵古都西安,今天我送女兒上大學(xué)。 六年來,女兒上學(xué)一直是我...
    一笑而過C閱讀 1,058評論 2 1
  • 從得到App里聽來的一本書。很專業(yè)的東西寫的很易懂,于是決定拿來做分享。這本書是以一個很有意思的比喻開始的,即「簡...
    Shayire閱讀 1,132評論 1 1

友情鏈接更多精彩內(nèi)容