Okhttp 思考


Okhttp 基礎(chǔ)知識(shí)導(dǎo)圖

Okhttp 框架

Okhttp 使用
1兢哭,創(chuàng)建一個(gè)客戶端领舰。
2,創(chuàng)建一個(gè)請(qǐng)求迟螺。
3冲秽,發(fā)起請(qǐng)求(入?yún)⒒卣{(diào))。

Request request = new Request.Builder().url( "http://xxxxx").build();
//創(chuàng)建客戶端
OkHttpClient mOkHttpClient= new OkHttpClient.Builder()
        .connectTimeout(30000, TimeUnit.MICROSECONDS)
        .readTimeout(30000, TimeUnit.MICROSECONDS)
        .writeTimeout(30000, TimeUnit.MICROSECONDS).build();
//創(chuàng)建請(qǐng)求
Call mCall= mOkHttpClient.newCall(request);
//請(qǐng)求+回調(diào)
mCall.enqueue(new Callback() {
    @Override
    public void onFailure(Call arg0, IOException arg1) {
    }
    @Override
    public void onResponse(Call arg0, Response arg1) throws IOException {
    }
});

一矩父、任務(wù)分發(fā)

網(wǎng)絡(luò)請(qǐng)求不能占用主線程資源锉桑,在多個(gè)請(qǐng)求并發(fā)條件下,需要一個(gè)管理器實(shí)現(xiàn)任務(wù)分發(fā)窍株,將任務(wù)交給線程池民轴。

任務(wù)分發(fā)

1攻柠,RealCall 類
Request 類是一個(gè)請(qǐng)求實(shí)體,配置 url后裸,組裝 Header瑰钮,請(qǐng)求體RequestBody, method 支持 GET微驶、HEAD浪谴、POST、DELETE因苹、PUT苟耻、PATCH。
每個(gè) Request 實(shí)體創(chuàng)建一個(gè) RealCall 對(duì)象容燕,負(fù)責(zé)一個(gè)具體請(qǐng)求的 http 事務(wù)梁呈。

@Override
public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
}

Call 接口,內(nèi)部工廠 Call.Factory 的 newCall() 方法創(chuàng)建蘸秘,OkHttpClient 類實(shí)現(xiàn)工廠接口官卡,創(chuàng)建 RealCall。
RealCall 類同步方法醋虏。

@Override
public Response execute() throws IOException {
    synchronized (this) {
    ...
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();//同步寻咒,耗時(shí)處
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
}

異步方法。

@Override
public void enqueue(Callback responseCallback) {
    synchronized (this) {
    ...
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

execute() 方法颈嚼,同步請(qǐng)求需要自己實(shí)現(xiàn)線程池毛秘。enqueue() 方法,創(chuàng)建一個(gè)異步任務(wù)阻课,交給分發(fā)者叫挟,Dispatcher 類分發(fā),(同步或異步)限煞。
2抹恳,Dispatcher 分發(fā)者
Dispatcher 類控制多個(gè) http 請(qǐng)求的節(jié)奏,負(fù)責(zé) RealCall 的并發(fā)署驻,管理(等待/運(yùn)行)隊(duì)列奋献,線程池調(diào)度。

請(qǐng)求分發(fā)流程
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
}

同步分發(fā)旺上,將 RealCall 對(duì)象加入同步隊(duì)列瓶蚂,表示 RealCall 正在執(zhí)行。

synchronized void enqueue(AsyncCall call) {
    //maxRequests=64宣吱,maxRequestsPerHost=5
    if (runningAsyncCalls.size() < maxRequests && 
            runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);//加入運(yùn)行隊(duì)列
      executorService().execute(call);//線程池執(zhí)行任務(wù)
    } else {
      readyAsyncCalls.add(call);
    }
}

異步分發(fā)窃这,maxRequests:最大請(qǐng)求數(shù)量,maxRequestsPerHost:每個(gè)主機(jī)最大并發(fā)請(qǐng)求數(shù)量征候。
滿足運(yùn)行時(shí)杭攻,將 AsyncCall (Runnable 任務(wù)洒试,RealCall 內(nèi)部類),加入運(yùn)行隊(duì)列朴上,派發(fā)線程池處理垒棋,不滿足運(yùn)行時(shí),加入等待隊(duì)列痪宰。
3叼架,請(qǐng)求結(jié)束
每次(同步/異步)請(qǐng)求完成時(shí),在 try/finally() 方法結(jié)束衣撬,Dispatcher 類的 finished()方法乖订。

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      //同步隊(duì)列刪除RealCall,異步運(yùn)行隊(duì)列刪除AsyncCall任務(wù)
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();//異步promoteCalls參數(shù)是true
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }
    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
}

異步請(qǐng)求時(shí)具练,繼續(xù)查詢等待隊(duì)列中的請(qǐng)求乍构。

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return;
    if (readyAsyncCalls.isEmpty()) return; 
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall call = i.next();
        if (runningCallsForHost(call) < maxRequestsPerHost) {
            i.remove();//滿足條件,等待隊(duì)列刪除
            runningAsyncCalls.add(call);//加入運(yùn)行中隊(duì)列
            executorService().execute(call);//開(kāi)始執(zhí)行
        }
        if (runningAsyncCalls.size() >= maxRequests) return; 
    }
}

遍歷等待隊(duì)列的任務(wù)扛点,滿足運(yùn)行條件時(shí)哥遮,從等待隊(duì)列刪除,加入運(yùn)行隊(duì)列陵究,派發(fā)線程池眠饮。

二、線程池管理

分發(fā)者內(nèi)部閥值[0, Integer.MAX_VALUE]的緩存線程池铜邮。

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;
}
  • 不保留 corePoolSize 核心線程仪召,最大可創(chuàng)建線程 MAX_VALUE。
  • 工作線程 keepAliveTime 設(shè)置60存活期線程復(fù)用松蒜。
  • SynchronousQueue 阻塞隊(duì)列不存儲(chǔ)元素扔茅,管道。

大量http請(qǐng)求并發(fā)時(shí)秸苗,線程池啟動(dòng)多個(gè)工作線程(臨時(shí)線程)召娜,確保每個(gè)任務(wù)都有線程及時(shí)處理。
線程執(zhí)行主體是 NamedRunnable 類 run() 方法难述,子類是運(yùn)行隊(duì)列中的 AsyncCall任務(wù)類萤晴,實(shí)現(xiàn) execute() 抽象方法吐句。
AsyncCall 構(gòu)造方法胁后,傳入外部Callback回調(diào)對(duì)象。

@Override
protected void execute() {
    boolean signalledCallback = false;
    try {
        //觸發(fā)RealCall類的方法嗦枢,AsyncCall任務(wù)類定義在RealCall類內(nèi)部攀芯。
        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) {
    } finally {
        //異步請(qǐng)求結(jié)束后finish。
        client.dispatcher().finished(this);
    }
}

外部 RealCall 類的 getResponseWithInterceptorChain() 方法文虏,(同步/異步)請(qǐng)求耗時(shí)操作侣诺,返回 Response 實(shí)體殖演。

Response getResponseWithInterceptorChain() throws IOException {
    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);
}

創(chuàng)建了一系列攔截器,鏈?zhǔn)教幚砟暝В彺媾烤谩⑦B接都通過(guò)攔截器鏈的節(jié)點(diǎn)實(shí)現(xiàn)。搔确、

三彼棍、攔截器

攔截器順序

客戶端自定義攔截器
retryAndFollowUpInterceptor 攔截器
BridgeInterceptor 橋接
CacheInterceptor 緩存
ConnectInterceptor 連接
networkInterceptors 網(wǎng)絡(luò)
CallServerInterceptor 數(shù)據(jù)流讀寫

RealInterceptorChain 類是在攔截器之間傳遞的節(jié)點(diǎn),第一次創(chuàng)建時(shí)膳算,index 初始是0座硕,入?yún)鬟f originalRequest 原始請(qǐng)求與攔截器列表。
在 RealInterceptorChain 類的 proceed() 方法涕蜂,開(kāi)始鏈?zhǔn)教幚砘遥脭r截器表中各層攔截處理 Request 請(qǐng)求和 Response 回復(fù)。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, 
                    RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
    ..
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
        //拋出異常
    }
    if (response == null) {
        //拋出異常
    }
    return response;
}

查找攔截器鏈表 index 索引的 Interceptor机隙,新建 index++ 的 RealInterceptorChain 節(jié)點(diǎn)蜘拉,觸發(fā) Interceptor的intercept() 方法,傳遞新 Chain有鹿。
列表中每一個(gè) Interceptor 攔截器的 intercept() 方法诸尽。

  • 處理 Request 請(qǐng)求
  • Chain 節(jié)點(diǎn)的 proceed() 方法
  • 處理 Response 答復(fù)

每一個(gè) Chain 節(jié)點(diǎn) proceed() 方法邏輯。

  • 查找下一個(gè)攔截器
  • 創(chuàng)建新 Chain 節(jié)點(diǎn)
  • 攔截器 intercept() 方法印颤,傳入新節(jié)點(diǎn)

當(dāng) index++索引到達(dá)最后一個(gè)攔截器 CallServerInterceptor 時(shí)您机,真正向 Server 發(fā)送數(shù)據(jù),獲取 Response年局,然后遞歸一層層按原路返回际看。
攔截器列表前部分 Interceptor 優(yōu)先處理原始 Request,后部分優(yōu)先處理原始 Response矢否。

攔截器處理流程
public interface Interceptor {
    Response intercept(Chain chain) throws IOException;
    interface Chain {
        Request request();
        Response proceed(Request request) throws IOException;
        Connection connection();
    }
}

攔截器設(shè)計(jì)是一個(gè)遞歸調(diào)用過(guò)程仲闽,Chain 對(duì)象是鏈節(jié)點(diǎn),proceed() 方法處理時(shí)僵朗,創(chuàng)建 index++的 Chain 新節(jié)點(diǎn)赖欣,傳遞給下一個(gè)攔截器。
proceed() 方法流入 Request 請(qǐng)求验庙,流出 Response 答復(fù)顶吮,攔截器目的是層層截?cái)嗔魅肱c流出,先截流粪薛,加工處理悴了,再放流。

四、緩存設(shè)計(jì)

Okhttp 緩存設(shè)計(jì)

五湃交、連接池

Okhttp 連接池

六熟空、數(shù)據(jù)流

Okhttp 數(shù)據(jù)流


任重而道遠(yuǎn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市搞莺,隨后出現(xiàn)的幾起案子息罗,更是在濱河造成了極大的恐慌,老刑警劉巖才沧,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阱当,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡糜工,警方通過(guò)查閱死者的電腦和手機(jī)弊添,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捌木,“玉大人油坝,你說(shuō)我怎么就攤上這事∨亳桑” “怎么了澈圈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)帆啃。 經(jīng)常有香客問(wèn)我瞬女,道長(zhǎng),這世上最難降的妖魔是什么努潘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任诽偷,我火速辦了婚禮,結(jié)果婚禮上疯坤,老公的妹妹穿的比我還像新娘报慕。我一直安慰自己,他們只是感情好压怠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布眠冈。 她就那樣靜靜地躺著,像睡著了一般菌瘫。 火紅的嫁衣襯著肌膚如雪蜗顽。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天雨让,我揣著相機(jī)與錄音雇盖,去河邊找鬼。 笑死宫患,一個(gè)胖子當(dāng)著我的面吹牛刊懈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娃闲,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼虚汛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了皇帮?” 一聲冷哼從身側(cè)響起卷哩,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎属拾,沒(méi)想到半個(gè)月后将谊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渐白,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年尊浓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纯衍。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡栋齿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出襟诸,到底是詐尸還是另有隱情瓦堵,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布歌亲,位于F島的核電站菇用,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏陷揪。R本人自食惡果不足惜惋鸥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望悍缠。 院中可真熱鬧揩慕,春花似錦、人聲如沸扮休。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)玷坠。三九已至蜗搔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間八堡,已是汗流浹背樟凄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留兄渺,地道東北人缝龄。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親叔壤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞎饲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容