Okhttp 源碼解析

Okhttp解析

總體思想

分析源碼,首先要熟悉用例澈圈,由上到下一層一層剝開源碼它掂,初步了解項(xiàng)目的框架巴帮,然后再細(xì)看代碼的實(shí)現(xiàn)細(xì)節(jié)∷萜現(xiàn)在試著分析 OKhttp 的源碼,下面代碼是來至 OKhttp官網(wǎng)榕茧。

GET A URL

OkHttpClient client = new OkHttpClient(); //(1)

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();                           //(2)

  Response response = client.newCall(request).execute();  //(3)
  return response.body().string();                        //(4)
}
  1. 創(chuàng)建一個 OkHttpClient 對象垃沦。
  2. 創(chuàng)建一個 Request 對象,可以設(shè)置 URL 等網(wǎng)絡(luò)配置用押。
  3. 調(diào)用 OkHttpClient 的 newCall() 方法肢簿,并把自定義配置的Request對象作為參數(shù)傳進(jìn)去。到處為止程序成功的將需要的請求放在了隊(duì)列中蜻拨,執(zhí)行 execute() 方法開始向服務(wù)器發(fā)起請求池充,服務(wù)器返回的信息轉(zhuǎn)化為 Response 對象。
  4. 返回 response 對象的 body 主體信息缎讼。

一張來至piasy的流程圖:

image

細(xì)節(jié)分析

創(chuàng)建 OkHttpClient 對象

OkHttpClient client = new OkHttpClient();

OkHttpClient.class 里面的兩個構(gòu)造函數(shù):

public OkHttpClient() {
    this(new Builder());
  }
  
private OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    .
    .
    .
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
  }

上面可以看出收夸,new OkHttpClient() 是調(diào)用 OkHttpClient.class 另外一個 private 的構(gòu)造函數(shù) OkHttpClient(Builder builder) ,其中 Builder 是 OkHttpClient 的一個內(nèi)部類休涤,Builder 是使用了構(gòu)造者模式咱圆,里面包含了一些配置相關(guān)的字段如下:

    final Dispatcher dispatcher;  //分發(fā)器
    final Proxy proxy; //代理
    final List<Protocol> protocols; //協(xié)議
    final List<ConnectionSpec> connectionSpecs; //傳輸層版本和連接協(xié)議
    final List<Interceptor> interceptors; //攔截器
    final List<Interceptor> networkInterceptors; //網(wǎng)絡(luò)攔截器
    .
    .
    .
    final int readTimeout; //read 超時(shí)
    final int writeTimeout; //write 超時(shí)

創(chuàng)建 Request 對象

創(chuàng)建完 OkHttpClient 對象后笛辟,就需要我們創(chuàng)建一個Request功氨,Request 作用就是承載用戶的請求,最簡單的也是必須的做法是設(shè)置 Request 的 URL手幢。同樣和創(chuàng)建 OkHttpClient 對象一樣捷凄,Request 也是使用 構(gòu)造者模式,其中包含了 URL, header, body等字段围来,簡單看看源碼中的 Request 的構(gòu)造函數(shù):

 private Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }

真正開始工作啦

同步請求

最上面的官方示例代碼是一個同步的網(wǎng)絡(luò)請求跺涤,接著我們一步一步拆解代碼。

//官方示例代碼
Response response = client.newCall(request).execute(); 
//OkHttpClient.class
public Call newCall(Request request) {
    return new RealCall(this, request);
  }

//Recall.class
  protected RealCall(OkHttpClient client, Request originalRequest) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client);
  }
  
    @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 {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

在 OkhttpClient 代碼里面看到监透,newCall() 函數(shù)返回一個 Call 對象桶错,其實(shí) Call 是一個接口,而我們的沒一次請求都是轉(zhuǎn)載在一個 Call 對象中胀蛮。其中看到返回的是一個 RealCall 對象院刁,由這里看到 RealCall 是 Call 的一個實(shí)現(xiàn)類。

Ok粪狼,接著看看 RealCall 對象的 execute() 方法退腥,可以看到這樣一行最重要的代碼 Response response = getResponseWithInterceptorChain() ,通過這一個方法可得到從服務(wù)器返回的一個 Response 對象再榄。從這個函數(shù)的名字可以推測狡刘,這是一個接一個的鏈?zhǔn)秸{(diào)用,接下來也可以從源碼發(fā)現(xiàn)困鸥,這個地方使用了責(zé)任鏈模式

//RealCall.call
private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(
        retryAndFollowUpInterceptor.isForWebSocket()));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

上的代碼總的功能就是往一個 list 里面一個一個的添加 interceptor 嗅蔬,interceptor 是一個抽象的接口,而代碼里添加的各種各樣的攔截器都是對 interceptor 接口不同的實(shí)現(xiàn)。例如购城,首先添加用戶自己配置的interceptor吕座,然后添加 retryAndFollowUpInterceptor 主要負(fù)責(zé)重定向和失敗重連,接著添加 BridgeInterceptor 主要負(fù)責(zé)轉(zhuǎn)化用戶配置的url瘪板,header等配置生成一個服務(wù)器能接受的文本格式吴趴。列表中 CacheInterceptor 我們大概能猜出它的作用,這是一個緩存攔截器侮攀,根據(jù) url 讀取緩存中的數(shù)據(jù)锣枝,如果有結(jié)果就在這里砍斷鏈?zhǔn)秸{(diào)用,成功返回結(jié)果兰英。 ConnectInterceptor 則是開始向服務(wù)器發(fā)起連接撇叁。CallServerInterceptor 是正式與服務(wù)器發(fā)生關(guān)系,也是從這個攔截器中返回最終的 Response 結(jié)果畦贸。

當(dāng)然陨闹,要發(fā)生上述的所有動作必須有一個起點(diǎn),chain.proceed(originalRequest) 就是整個鏈路的入口薄坏,就在這里開始一個環(huán)節(jié)扣著一個環(huán)節(jié)執(zhí)行下去趋厉。放回最終的結(jié)果,這不胶坠,一次完整的同步網(wǎng)絡(luò)請求就完成了君账。

異步請求

上面是一個同步請求的解析,現(xiàn)在來談?wù)劗惒秸埱笊蛏疲秸埱笥挟惽ぶ钕缡蟛糠值牧鞒虒?shí)現(xiàn)還是沿用同一份代碼,最大的不同是異步請求加入了 dispatcher 闻牡,見面知其意净赴,就是由這個類來負(fù)責(zé)分發(fā)用戶的請求。按照上面的思路罩润,首先看看一個異步的示例

//異步示例
OkHttpClient client=new OkHttpClient();

Request request = new Request.Builder()
                        .url(url)
                        .build();

client.newCall(request)
        .enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }

            @Override
            public void onFailure(Call call, IOException e) {

            }
        });

Request 和 OkHttpClient 創(chuàng)建和同步請求一致玖翅,重點(diǎn)在與 RealCall() 接口里面的 enqueue(CallBack callback) 方法。傳入的當(dāng)然是一個 CallBack 接口對象用戶需要服務(wù)器返回的消息通過這個對象傳遞回來哨啃。復(fù)寫的 onResponse() 成功獲取服務(wù)器端返回的結(jié)果烧栋,onFailure() 返回錯誤失敗的信息,下面接著看 enqueued() 調(diào)用棧拳球。

//RealCall.class
  @Override 
  public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
    //RealCall 內(nèi)部類繼承NamedRunnable(代碼向下滑)
  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    private AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl().toString());
      this.responseCallback = responseCallback;
    }
    
    @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 {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }
  
//Dispatcher.class
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  
 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
//NamedRunnable.class
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

RealCall 類 enqueue(Callback responseCallback) 中可以看到 dispatcher() 方法 审姓,dispatcher 也有一個 enqueue(AsyncCall call) 方法, 在這個 enqueue 方法里有一個列表并且執(zhí)行 AsynCall 祝峻,如何執(zhí)行 AsynCall 呢? AsynCall 是 RealCall 的內(nèi)部類魔吐,集成 NamedRunnable扎筒,NamedRunnable 繼承 Runanble。最終的與服務(wù)器發(fā)生交互的動作就在 AsynCall 的 execute() 方法內(nèi)酬姆,這里就看到了熟悉的一行代碼 :

Response response = getResponseWithInterceptorChain();

執(zhí)行的流程就和同步的請求一模一樣嗜桌,不同點(diǎn)是再取得的服務(wù)器最終結(jié)果 Callback 接口放回給用戶。

總結(jié)

更好讀懂 OKHttp 的源碼關(guān)鍵是要了解常用的設(shè)計(jì)模式辞色,用構(gòu)造模式創(chuàng)建出 OkHttpClient 和 Request 對象骨宠,使用責(zé)任鏈模式完成一個鏈?zhǔn)降恼{(diào)用。個人認(rèn)為這就是 OkHttp 最基本的框架相满。代碼的還有許多設(shè)計(jì)精良的部分层亿,還未還好細(xì)讀,目前還不把每一部分做到庖丁解牛的境界立美,有時(shí)間再一另幫 Blog 做另外的分析匿又。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市建蹄,隨后出現(xiàn)的幾起案子碌更,更是在濱河造成了極大的恐慌,老刑警劉巖洞慎,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痛单,死亡現(xiàn)場離奇詭異,居然都是意外死亡拢蛋,警方通過查閱死者的電腦和手機(jī)桦他,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門蔫巩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谆棱,“玉大人,你說我怎么就攤上這事圆仔±疲” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵坪郭,是天一觀的道長个从。 經(jīng)常有香客問我,道長歪沃,這世上最難降的妖魔是什么嗦锐? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮沪曙,結(jié)果婚禮上奕污,老公的妹妹穿的比我還像新娘。我一直安慰自己液走,他們只是感情好碳默,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布贾陷。 她就那樣靜靜地躺著,像睡著了一般嘱根。 火紅的嫁衣襯著肌膚如雪髓废。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天该抒,我揣著相機(jī)與錄音慌洪,去河邊找鬼。 笑死凑保,一個胖子當(dāng)著我的面吹牛蒋譬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愉适,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼犯助,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了维咸?” 一聲冷哼從身側(cè)響起剂买,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎癌蓖,沒想到半個月后瞬哼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡租副,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年坐慰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片用僧。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡结胀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出责循,到底是詐尸還是另有隱情糟港,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布院仿,位于F島的核電站秸抚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏歹垫。R本人自食惡果不足惜剥汤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望排惨。 院中可真熱鬧吭敢,春花似錦、人聲如沸若贮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蠢沿,卻和暖如春伸头,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舷蟀。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工恤磷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人野宜。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓扫步,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匈子。 傳聞我的和親對象是個殘疾皇子河胎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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