亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

OKHttp使用詳解

 更新時間:2024年01月05日 10:50:14   作者:-Sloth-  
OkHttp 是一套處理 HTTP 網(wǎng)絡(luò)請求的依賴庫,由 Square 公司設(shè)計研發(fā)并開源,目前可以在 Java 和 Kotlin 中使用,這篇文章主要介紹了OKHttp詳解,需要的朋友可以參考下

OkHttp 是一套處理 HTTP 網(wǎng)絡(luò)請求的依賴庫,由 Square 公司設(shè)計研發(fā)并開源,目前可以在 Java 和 Kotlin 中使用。對于 Android App 來說,OkHttp 現(xiàn)在幾乎已經(jīng)占據(jù)了所有的網(wǎng)絡(luò)請求操作,RetroFit + OkHttp 實現(xiàn)網(wǎng)絡(luò)請求似乎成了一種標(biāo)配。因此它也是每一個 Android 開發(fā)工程師的必備技能,了解其內(nèi)部實現(xiàn)原理可以更好地進(jìn)行功能擴(kuò)展、封裝以及優(yōu)化。

網(wǎng)絡(luò)請求流程分析

先看下 OkHttp 的基本使用:

除了直接 new OkHttpClient 之外,還可以使用內(nèi)部工廠類 Builder 來設(shè)置 OkHttpClient。如下所示:

請求操作的起點從 OkHttpClient.newCall().enqueue() 方法開始:

newCall

RealCall.enqueue

調(diào)用 Dispatcher 的入隊方法,執(zhí)行一個異步網(wǎng)絡(luò)請求的操作。

可以看出,最終請求操作是委托給 Dispatcher的enqueue 方法內(nèi)實現(xiàn)的。

Dispatcher 是 OkHttpClient 的調(diào)度器,是一種門戶模式。主要用來實現(xiàn)執(zhí)行、取消異步請求操作。本質(zhì)上是內(nèi)部維護(hù)了一個線程池去執(zhí)行異步操作,

并且在 Dispatcher 內(nèi)部根據(jù)一定的策略,保證最大并發(fā)個數(shù)、同一 host 主機允許執(zhí)行請求的線程個數(shù)等。

Dispatcher的enqueue 方法的具體實現(xiàn)如下:

可以看出,實際上就是使用線程池執(zhí)行了一個 AsyncCall,而 AsyncCall 實現(xiàn)了 Runnable 接口,因此整個操作會在一個子線程(非 UI 線程)中執(zhí)行。

繼續(xù)查看 AsyncCall 中的 run 方法如下:

在 run 方法中執(zhí)行了另一個 execute 方法,而真正獲取請求結(jié)果的方法是在 getResponseWithInterceptorChain 方法中,從名字也能看出其內(nèi)部是一個攔截器的調(diào)用鏈,具體代碼如下:

每一個攔截器的作用如下。

  • BridgeInterceptor:主要對 Request 中的 Head 設(shè)置默認(rèn)值,比如 Content-Type、Keep-Alive、Cookie 等。
  • CacheInterceptor:負(fù)責(zé) HTTP 請求的緩存處理。
  • ConnectInterceptor:負(fù)責(zé)建立與服務(wù)器地址之間的連接,也就是 TCP 鏈接。
  • CallServerInterceptor:負(fù)責(zé)向服務(wù)器發(fā)送請求,并從服務(wù)器拿到遠(yuǎn)端數(shù)據(jù)結(jié)果。
  • 在添加上述幾個攔截器之前,會調(diào)用 client.interceptors 將開發(fā)人員設(shè)置的攔截器添加到列表當(dāng)中。

對于 Request 的 Head 以及 TCP 鏈接,我們能控制修改的成分不是很多。

總結(jié)

1.Okhttp是對Socket的封裝。有三個主要的類,Request,Response,Call

2.默認(rèn)使用new OkHttpClient() 創(chuàng)建初client對象。如果需要初始化網(wǎng)絡(luò)請求的參數(shù),如timeout,interceptor等,可以創(chuàng)建Builder,通過builder.build() 創(chuàng)建初client對象。

3.使用new Request.Builder().url().builder()創(chuàng)建初requst對象。

4.通過client.newCall()創(chuàng)建出call對象,同步使用call.excute(), 異步使用call,enqueue(). 這里Call是個接口,具體的實現(xiàn)在RealCall這個實現(xiàn)類里面。

Okhttp的高效體現(xiàn)在,okhttp內(nèi)有個Dispatcher類,是okhttp內(nèi)部維護(hù)的一個線程池,對最大連接數(shù),host最大訪問量做了初始定義。維護(hù)3個隊列及1個線程池

readyAsyncCalls

待訪問請求隊列,里面存儲準(zhǔn)備執(zhí)行的請求。

runningAsyncCalls

異步請求隊列,里面存儲正在執(zhí)行,包含已經(jīng)取消但是還沒有結(jié)束的請求。

runningSyncCalls

同步請求隊列,正在執(zhí)行的請求,包含已經(jīng)取消但是還沒有結(jié)束的請求。

ExecutorService

線程池,最小0,最大Max的線程池

在執(zhí)行call.excute()的時候,調(diào)用到realcall類里的excute方法,這個是同步方法,在方法的第一行就加了鎖,判斷executed標(biāo)記,如果是true就拋出異常,保證一個請求只被執(zhí)行一次。false的話繼續(xù)向下執(zhí)行。調(diào)用client.dispatcher.excute()進(jìn)入到dispatcher類中,向runningSyncCalls隊列中添加當(dāng)前這個請求。執(zhí)行結(jié)束會調(diào)用finished方法

如果是異步操作,會創(chuàng)建一個RealCall.AsyncCall對象,AsyncCall繼承的NamedRunnable接口,NamedRunnable是個runnable。進(jìn)入到Dispatcher的enqueue()方法中,首先判斷線程池中線程的數(shù)據(jù),host的訪問量,如果都沒有達(dá)到那么加入到runningAsyncCalls中,并執(zhí)行。否則加入到readyAsyncCalls隊列中。
finished方法,如果是異步操作,promoteCall方法,promoteCalls()中用迭代器遍歷readyAsyncCalls 然后加入到runningAsyncCalls

RealConnection

真正的連接操作類,對soket封裝,http1/http2的選擇,ssl協(xié)議等等信息。一個recalconnection就是一次鏈接

ConnectionPool

鏈接池,管理http1/http2的連接,同一個address共享一個connection,實現(xiàn)鏈接的復(fù)用。

StreamAlloction

保存了鏈接信息,address,HttpCodec,realconnection,connectionpool等信息

常見問題:

OKHttp有哪些攔截器,分別起什么作用?

OKHTTP的攔截器是把所有的攔截器放到一個list里,然后每次依次執(zhí)行攔截器,并且在每個攔截器分成三部分:

  • 預(yù)處理攔截器內(nèi)容
  • 通過proceed方法把請求交給下一個攔截器
  • 下一個攔截器處理完成并返回,后續(xù)處理工作。

這樣依次下去就形成了一個鏈?zhǔn)秸{(diào)用,看看源碼,具體有哪些攔截器:

  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);
    return chain.proceed(originalRequest);
  }

根據(jù)源碼可知,一共七個攔截器:

  • addInterceptor(Interceptor),這是由開發(fā)者設(shè)置的,會按照開發(fā)者的要求,在所有的攔截器處理之前進(jìn)行最早的攔截處理,比如一些公共參數(shù),Header都可以在這里添加。
  • RetryAndFollowUpInterceptor,這里會對連接做一些初始化工作,以及請求失敗的充實工作,重定向的后續(xù)請求工作。跟他的名字一樣,就是做重試工作還有一些連接跟蹤工作。
  • BridgeInterceptor,這里會為用戶構(gòu)建一個能夠進(jìn)行網(wǎng)絡(luò)訪問的請求,同時后續(xù)工作將網(wǎng)絡(luò)請求回來的響應(yīng)Response轉(zhuǎn)化為用戶可用的Response,比如添加文件類型,content-length計算添加,gzip解包。
  • CacheInterceptor,這里主要是處理cache相關(guān)處理,會根據(jù)OkHttpClient對象的配置以及緩存策略對請求值進(jìn)行緩存,而且如果本地有了可?的Cache,就可以在沒有網(wǎng)絡(luò)交互的情況下就返回緩存結(jié)果。
  • ConnectInterceptor,這里主要就是負(fù)責(zé)建立連接了,會建立TCP連接或者TLS連接,以及負(fù)責(zé)編碼解碼的HttpCodec
  • networkInterceptors,這里也是開發(fā)者自己設(shè)置的,所以本質(zhì)上和第一個攔截器差不多,但是由于位置不同,所以用處也不同。這個位置添加的攔截器可以看到請求和響應(yīng)的數(shù)據(jù)了,所以可以做一些網(wǎng)絡(luò)調(diào)試。
  • CallServerInterceptor,這里就是進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)的請求和響應(yīng)了,也就是實際的網(wǎng)絡(luò)I/O操作,通過socket讀寫數(shù)據(jù)。

OkHttp怎么實現(xiàn)連接池

為什么需要連接池?

頻繁的進(jìn)行建立Sokcet連接和斷開Socket是非常消耗網(wǎng)絡(luò)資源和浪費時間的,所以HTTP中的keepalive連接對于降低延遲和提升速度有非常重要的作用。keepalive機制是什么呢?也就是可以在一次TCP連接中可以持續(xù)發(fā)送多份數(shù)據(jù)而不會斷開連接。所以連接的多次使用,也就是復(fù)用就變得格外重要了,而復(fù)用連接就需要對連接進(jìn)行管理,于是就有了連接池的概念。

OkHttp中使用ConectionPool實現(xiàn)連接池,默認(rèn)支持5個并發(fā)KeepAlive,默認(rèn)鏈路生命為5分鐘。

怎么實現(xiàn)的?

1)首先,ConectionPool中維護(hù)了一個雙端隊列Deque,也就是兩端都可以進(jìn)出的隊列,用來存儲連接。
2)然后在ConnectInterceptor,也就是負(fù)責(zé)建立連接的攔截器中,首先會找可用連接,也就是從連接池中去獲取連接,具體的就是會調(diào)用到ConectionPool的get方法。

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

也就是遍歷了雙端隊列,如果連接有效,就會調(diào)用acquire方法計數(shù)并返回這個連接。

3)如果沒找到可用連接,就會創(chuàng)建新連接,并會把這個建立的連接加入到雙端隊列中,同時開始運行線程池中的線程,其實就是調(diào)用了ConectionPool的put方法。

public final class ConnectionPool {
    void put(RealConnection connection) {
        if (!cleanupRunning) {
            //沒有連接的時候調(diào)用
            cleanupRunning = true;
            executor.execute(cleanupRunnable);
        }
        connections.add(connection);
    }
}

  其實這個線程池中只有一個線程,是用來清理連接的,也就是上述的cleanupRunnable

private final Runnable cleanupRunnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                //執(zhí)行清理,并返回下次需要清理的時間。
                long waitNanos = cleanup(System.nanoTime());
                if (waitNanos == -1) return;
                if (waitNanos > 0) {
                    long waitMillis = waitNanos / 1000000L;
                    waitNanos -= (waitMillis * 1000000L);
                    synchronized (ConnectionPool.this) {
                        //在timeout時間內(nèi)釋放鎖
                        try {
                            ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
        }
    };

這個runnable會不停的調(diào)用cleanup方法清理線程池,并返回下一次清理的時間間隔,然后進(jìn)入wait等待。

怎么清理的呢?看看源碼:

long cleanup(long now) {
    synchronized (this) {
      //遍歷連接
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
        //檢查連接是否是空閑狀態(tài),
        //不是,則inUseConnectionCount + 1
        //是 ,則idleConnectionCount + 1
        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;
        }
      }
      //如果超過keepAliveDurationNs或maxIdleConnections,
      //從雙端隊列connections中移除
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {      
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {      //如果空閑連接次數(shù)>0,返回將要到期的時間
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // 連接依然在使用中,返回保持連接的周期5分鐘
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
        cleanupRunning = false;
        return -1;
      }
    }
    closeQuietly(longestIdleConnection.socket());
    // Cleanup again immediately.
    return 0;
  }

也就是當(dāng)如果空閑連接maxIdleConnections超過5個或者keepalive時間大于5分鐘,則將該連接清理掉。

4)這里有個問題,怎樣屬于空閑連接?

其實就是有關(guān)剛才說到的一個方法acquire計數(shù)方法:

  public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();
    this.connection = connection;
    this.reportedAcquired = reportedAcquired;
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }

RealConnection中,有一個StreamAllocation虛引用列表allocations。每創(chuàng)建一個連接,就會把連接對應(yīng)的StreamAllocationReference添加進(jìn)該列表中,如果連接關(guān)閉以后就將該對象移除。

其實可以這樣理解,在上層反復(fù)調(diào)用acquire和release函數(shù),來增加或減少connection.allocations所維持的集合的大小,到最后如果size大于0,則代表RealConnection還在使用連接,如果size等于0,那就說明已經(jīng)處于空閑狀態(tài)了

5)連接池的工作就這么多,并不復(fù)雜,主要就是管理雙端隊列Deque<RealConnection>,可以用的連接就直接用,然后定期清理連接,同時通過對StreamAllocation的引用計數(shù)實現(xiàn)自動回收。

OkHttp里面用到了什么設(shè)計模式

責(zé)任鏈模式

這個不要太明顯,可以說是okhttp的精髓所在了,主要體現(xiàn)就是攔截器的使用,具體代碼可以看看上述的攔截器介紹。

建造者模式

在Okhttp中,建造者模式也是用的挺多的,主要用處是將對象的創(chuàng)建與表示相分離,用Builder組裝各項配置。
比如Request:

public class Request {
  public static class Builder {
    @Nullable HttpUrl url;
    String method;
    Headers.Builder headers;
    @Nullable RequestBody body;
    public Request build() {
      return new Request(this);
    }
  }
}

工廠模式

工廠模式和建造者模式類似,區(qū)別就在于工廠模式側(cè)重點在于對象的生成過程,而建造者模式主要是側(cè)重對象的各個參數(shù)配置。
例子有CacheInterceptor攔截器中又個CacheStrategy對象:

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;
      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }
 

觀察者模式

之前我寫過一篇文章,是關(guān)于Okhttp中websocket的使用,由于webSocket屬于長連接,所以需要進(jìn)行監(jiān)聽,這里是用到了觀察者模式:

  final WebSocketListener listener;
  @Override public void onReadMessage(String text) throws IOException {
    listener.onMessage(this, text);
  }
 

單例模式

這個就不舉例了,每個項目都會有

OkHttp中為什么使用構(gòu)建者模式?

使用多個簡單的對象一步一步構(gòu)建成一個復(fù)雜的對象;

  • 優(yōu)點: 當(dāng)內(nèi)部數(shù)據(jù)過于復(fù)雜的時候,可以非常方便的構(gòu)建出我們想要的對象,并且不是所有的參數(shù)我們都需要進(jìn)行傳遞;
  • 缺點: 代碼會有冗余

怎么設(shè)計一個自己的網(wǎng)絡(luò)訪問框架,為什么這么設(shè)計?

我目前還沒有正式設(shè)計過網(wǎng)絡(luò)訪問框架,

是我自己設(shè)計的話,我會從以下兩個方面考慮

  • 先參考現(xiàn)有的框架,找一個比較合適的框架作為啟動點,比如說,基于上面講到的okhttp的優(yōu)點,選擇okhttp的源碼進(jìn)行閱讀,并且將主線的流程抽取出,為什么這么做,因為okhttp里面雖然涉及到了很多的內(nèi)容,但是我們用到的內(nèi)容并不是特別多;保證先能運行起來一個基本的框架;
  • 考慮拓展,有了基本框架之后,我會按照我目前在項目中遇到的一些需求或者網(wǎng)路方面的問題,看看能不能基于我這個框架進(jìn)行優(yōu)化,比如服務(wù)器它設(shè)置的緩存策略,
    我應(yīng)該如何去編寫客戶端的緩存策略去對應(yīng)服務(wù)器的,還比如說,可能剛剛?cè)ソ⒒镜目蚣軙r,不會考慮HTTPS的問題,那么也會基于后來都要求https,進(jìn)行拓展;

為什么要基于Okhttp,就是因為它是基于Socket,從我個人角度講,如果能更底層的深入了解相關(guān)知識,這對我未來的技術(shù)有很大的幫助;

如何考慮app的安全性?

1:使用https協(xié)議進(jìn)行交互
2:數(shù)據(jù)交互時,根據(jù)業(yè)務(wù)分出哪些是敏感信息,凡是敏感信息使用對稱加密方式,如果是類似密碼的,則使用不可逆的加密方式;md5
3:考慮跟錢相關(guān),或者同等重要的數(shù)據(jù)接口,需要做多重驗證,比如:前端加密請求參數(shù),合并請求參數(shù)生成MD5碼,服務(wù)器端做多重認(rèn)證,最好能對比本地數(shù)據(jù)庫或者緩存之類的信息;
4:混淆,
5: app加固,dex文件進(jìn)行加密,這種方式,可以通過”內(nèi)存下載“,不安全,也只是為了增加破解難度;
6:將加密算法,一些核心數(shù)據(jù)添加到so文件中;

到此這篇關(guān)于OKHttp詳解的文章就介紹到這了,更多相關(guān)OKHttp詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論