Volley源碼解析

Volley介紹

Volley 是 Google 在 2013 Google I/O 大會(huì)上發(fā)布推出的 Android 異步網(wǎng)絡(luò)請(qǐng)求框架和圖片加載框架,其最明顯的一個(gè)優(yōu)點(diǎn)就是特別適合數(shù)據(jù)量不大但是通信頻繁的場(chǎng)景,最明顯的缺點(diǎn)就是大數(shù)據(jù)傳輸表現(xiàn)的很糟糕。(適合于數(shù)據(jù)量小,通信頻繁的網(wǎng)絡(luò)操作)

Volley提供了以下的便利功能

  • JSON數(shù)據(jù)和圖像等的異步下載;
  • 網(wǎng)絡(luò)請(qǐng)求排序(scheduling);
  • 網(wǎng)絡(luò)請(qǐng)求優(yōu)先級(jí)處理;
  • 緩存;
  • 多級(jí)別取消請(qǐng)求;
  • 與Activity生命周期聯(lián)動(dòng)(Activity結(jié)束時(shí)同時(shí)取消所有網(wǎng)絡(luò)請(qǐng)求);

newRequestQueue的使用

Volley的用法很簡(jiǎn)單,發(fā)起一條HTTP GET請(qǐng)求,然后接收HTTP響應(yīng)。首先需要獲取到一個(gè)RequestQueue對(duì)象,可以調(diào)用如下方法獲取到:

RequestQueue newQueue = Volley.newRequestQueue(context);

這里拿到的RequestQueue是一個(gè)請(qǐng)求隊(duì)列對(duì)象,它可以緩存所有的HTTP請(qǐng)求,然后按照一定的算法并發(fā)地發(fā)出這些請(qǐng)求。

RequestQueue內(nèi)部的設(shè)計(jì)就是非常合適高并發(fā)的,因此我們不必為每一次HTTP請(qǐng)求都創(chuàng)建一個(gè)RequestQueue對(duì)象,這是非常浪費(fèi)資源的,基本上在每一個(gè)需要和網(wǎng)絡(luò)交互的Activity中創(chuàng)建一個(gè)RequestQueue對(duì)象就足夠了。

Request的使用

Request是請(qǐng)求的基類,擁有以下子類:

  • ClearCacheRequest
  • JsonRequest
  • ImageRequest
  • StringRequest

在基類中定義了一個(gè)內(nèi)部接口類 Method 分別定義了集中常見的請(qǐng)求方式

public interface Method {
    int DEPRECATED_GET_OR_POST = -1;
    int GET = 0;
    int POST = 1;
    int PUT = 2;
    int DELETE = 3;
    int PATCH = 4;
}

同時(shí)還定義了一個(gè)枚舉 Priority 定義了一些優(yōu)先級(jí)的常量

public static enum Priority {
    LOW,
    NORMAL,
    HIGH,
    IMMEDIATE;

    private Priority() {
    }
}

StringRequestd的使用

StringRequest的構(gòu)造函數(shù)需要傳入四個(gè)參數(shù):

public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) {
    super(method, url, errorListener);
    this.mListener = listener;
}
  • 第一個(gè)參數(shù)是表示請(qǐng)求的方式 默認(rèn)是0 也就是 GET 請(qǐng)求
  • 第二個(gè)參數(shù)是目標(biāo)服務(wù)器的URL地址
  • 第三個(gè)參數(shù)是服務(wù)器響應(yīng)成功的回調(diào)
  • 第四個(gè)參數(shù)是服務(wù)器響應(yīng)失敗的回調(diào)

將這個(gè)StringRequest對(duì)象添加到RequestQueue里面就可以了。使用Volley時(shí),可以從任何線程開始請(qǐng)求,但響應(yīng)始終傳遞到了主線程上。
以下是閱讀Android Developer文檔中的VolleyDemo:

final TextView mTextView = (TextView) findViewById(R.id.text);
...

// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";

// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener() {
    @Override
    public void onResponse(String response) {
        // Display the first 500 characters of the response string.
        mTextView.setText("Response is: "+ response.substring(0,500));
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mTextView.setText("That didn't work!");
    }
});
// Add the request to the RequestQueue.
queue.add(stringRequest);

以上就用Volley發(fā)起了一個(gè)簡(jiǎn)單的HTTP 的 GET 請(qǐng)求。

Volley發(fā)出Request

Volley發(fā)出網(wǎng)絡(luò)請(qǐng)求,阻塞I/O和解析數(shù)據(jù)都是在子線程中完成的,您可以在任何線程添加網(wǎng)絡(luò)請(qǐng)求,但是響應(yīng)始終都是傳遞到主線程上。
分析一下 Android Developer 上的這幅圖:

volley-request.png
  • RequestQueue會(huì)維護(hù)一個(gè)緩存調(diào)度線程(CacheDispatcher)
  • 同時(shí)還會(huì)維護(hù)一網(wǎng)絡(luò)調(diào)度線程池(NetworkDispatcher[])->線程池默認(rèn)大小為4
  • Volley.newRequestQueue(context);的代碼中在data/data/包名/cache/目錄下創(chuàng)建了一個(gè)volley的文件夾用于存放緩存(本地緩存)
  • 同時(shí)創(chuàng)建一個(gè) RequestQueue 類,并調(diào)用了其start() 方法
BasicNetwork network1 = new BasicNetwork((HttpStack)stack);//將在后文提到
RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
queue1.start();
  • start()方法看上去像是線程開啟的方法,但是 RequestQueue 并不是一個(gè)Thread類,不過(guò)在當(dāng)中它干的就是開啟線程的事。
public void start() {
    this.stop();
    this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
    this.mCacheDispatcher.start();

    for(int i = 0; i < this.mDispatchers.length; ++i) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
        this.mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

沒(méi)錯(cuò),它逐一開啟了緩存調(diào)度線程和所有的網(wǎng)絡(luò)調(diào)度線程

  • 每一條線程就開始不斷的循環(huán)讀取cache隊(duì)列或者net隊(duì)列的頭

  • 當(dāng)一個(gè)Request被添加到隊(duì)列中的時(shí)候,cache線程會(huì)首先對(duì)這個(gè)請(qǐng)求進(jìn)行篩選,如果這個(gè)請(qǐng)求的內(nèi)筒可以在緩存中找到,并且沒(méi)有過(guò)期,cache線程會(huì)自己解析響應(yīng)的內(nèi)容,并分發(fā)到主線程(UI);

  • 如果緩存中沒(méi)有,這個(gè)request就會(huì)被加入到另一個(gè)NetworkQueue,所有真正準(zhǔn)備進(jìn)行網(wǎng)絡(luò)通信的request都在這里,第一個(gè)可用的net線程會(huì)從NetworkQueue中拿出一個(gè)request扔向服務(wù)器。當(dāng)響應(yīng)獲得數(shù)據(jù)之后,這個(gè)net線程會(huì)解析原始響應(yīng)數(shù)據(jù),寫入緩存,并把解析后的結(jié)果返回給主線程。

取消一個(gè)Request

要取消一個(gè)請(qǐng)求,調(diào)用cancel()即可。一旦取消,Volley保證你的響應(yīng)處理程序?qū)⒂肋h(yuǎn)不會(huì)被調(diào)用。這意味著在實(shí)踐中,你可以取消所有待定的請(qǐng)求在你Activity的onStop()方法中,你不必亂拋垃圾的響應(yīng)處理程序或者檢查getActivity() == NULL,無(wú)論onSaveInstanceState()是否已經(jīng)被調(diào)用。

你發(fā)出去的請(qǐng)求必須要自己保證是可控的,在這里還有一個(gè)更簡(jiǎn)單的方法:你可以標(biāo)記發(fā)送的每個(gè)請(qǐng)求對(duì)象,然后你可以使用這個(gè)標(biāo)簽來(lái)提供請(qǐng)求取消的范圍。

下面是一個(gè)使用字符串標(biāo)簽的例子:

  1. 定義您的標(biāo)簽同時(shí)將其添加到Request中
    public static final String TAG = "MyTag";
    StringRequest stringRequest; // Assume this exists.
    RequestQueue mRequestQueue; // Assume this exists.

     // Set the tag on the request.
     stringRequest.setTag(TAG);
     
     // Add the request to the RequestQueue.
     mRequestQueue.add(stringRequest);
    
  2. 在您的Activity的 onStop() 方法中,取消所有被標(biāo)記的Request

     @Override
     protected void onStop () {
         super.onStop();
         if (mRequestQueue != null) {
             mRequestQueue.cancelAll(TAG);
         }
     }
    

取消請(qǐng)求時(shí)要小心。如果你是根據(jù)你的響應(yīng)處理程序來(lái)推進(jìn)一個(gè)狀態(tài)或啟動(dòng)另一個(gè)進(jìn)程,你需要考慮到這個(gè)。同樣,響應(yīng)處理程序?qū)⒉粫?huì)被調(diào)用。

BasicNetwork

現(xiàn)在我們來(lái)說(shuō)說(shuō) BasicNetwork,前文在講 RequestQueue 的時(shí)候有所提到,其中 BasicNetwork 是創(chuàng)建 RequestQueue 對(duì)象時(shí)當(dāng)中的一個(gè)參數(shù)

  • BasicNetwork 是Volley的一個(gè)默認(rèn)網(wǎng)絡(luò)實(shí)現(xiàn),App連接HTTP客戶端必須要對(duì)其初始化。最主要的是實(shí)現(xiàn)了 Network 接口中的 NetworkResponse performRequest(Request<?> var1) throws VolleyError; 方法,其實(shí)這個(gè)接口中就只有這一個(gè)方法。
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
    long requestStart = SystemClock.elapsedRealtime();

          while(true) {
              HttpResponse httpResponse = null;
              Object responseContents = null;
              HashMap responseHeaders = new HashMap();
    
              try {
                  HashMap e = new HashMap();
                  this.addCacheHeaders(e, request.getCacheEntry());
                  httpResponse = this.mHttpStack.performRequest(request, e);
                  StatusLine statusCode2 = httpResponse.getStatusLine();
                  int networkResponse1 = statusCode2.getStatusCode();
                  Map responseHeaders1 = convertHeaders(httpResponse.getAllHeaders());
                  if(networkResponse1 == 304) {
                      return new NetworkResponse(304, request.getCacheEntry().data, responseHeaders1, true);
                  }
    
                  byte[] responseContents1 = this.entityToBytes(httpResponse.getEntity());
                  long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                  this.logSlowRequests(requestLifetime, request, responseContents1, statusCode2);
                  if(networkResponse1 != 200 && networkResponse1 != 201 && networkResponse1 != 202 && networkResponse1 != 204) {
                      throw new IOException();
                  }
    
                  return new NetworkResponse(networkResponse1, responseContents1, responseHeaders1, false);
              } catch (SocketTimeoutException var12) {
                  attemptRetryOnException("socket", request, new TimeoutError());
              } catch (ConnectTimeoutException var13) {
                  attemptRetryOnException("connection", request, new TimeoutError());
              } catch (MalformedURLException var14) {
                  throw new RuntimeException("Bad URL " + request.getUrl(), var14);
              } catch (IOException var15) {
                  boolean statusCode = false;
                  NetworkResponse networkResponse = null;
                  if(httpResponse == null) {
                      throw new NoConnectionError(var15);
                  }
    
                  int statusCode1 = httpResponse.getStatusLine().getStatusCode();
                  VolleyLog.e("Unexpected response code %d for %s", new Object[]{Integer.valueOf(statusCode1), request.getUrl()});
                  if(responseContents == null) {
                      throw new NetworkError(networkResponse);
                  }
    
                  networkResponse = new NetworkResponse(statusCode1, (byte[])responseContents, responseHeaders, false);
                  if(statusCode1 != 401 && statusCode1 != 403) {
                      if(statusCode1 != 400 && statusCode1 != 404 && statusCode1 != 406 && statusCode1 != 501) {
                          throw new ServerError(networkResponse);
                      }
      
                          throw new ClientError(networkResponse);
                      }
      
                  attemptRetryOnException("auth", request, new AuthFailureError(networkResponse));
              }
              }
         }
    
  • 這個(gè)方法是網(wǎng)絡(luò)請(qǐng)求的具體實(shí)現(xiàn),以一個(gè)while大循環(huán)包裹,其中httpResponse = this.mHttpStack.performRequest(request, e);是主要的網(wǎng)絡(luò)請(qǐng)求代碼,由HttpStack發(fā)出。在創(chuàng)建對(duì)象的時(shí)候傳入的對(duì)象。以下具體說(shuō)明

  • 創(chuàng)建 BasicNetwork 對(duì)象的時(shí)候,需要傳入一個(gè)HttpStack的對(duì)象。
    HttpStack stack;
    ...
    if(stack == null) {
    if(VERSION.SDK_INT >= 9) {
    stack = new HurlStack();
    } else {
    stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
    }
    }

      BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
    
  • 可以看出在 Froyo(2.2) 之前,采用基于 HttpClient 的 HttpClientStack,之后使用基于 HttpURLConnection 的 HurlStack,原因呢是因?yàn)镕royo(2.2) HttpURLConnection 有個(gè)重大 Bug,調(diào)用 close() 函數(shù)會(huì)影響連接池,導(dǎo)致連接復(fù)用失效,所以在 Froyo 之前使用 HttpURLConnection 需要關(guān)閉 keepAlive。

      private void disableConnectionReuseIfNecessary() {    
      // 這是一個(gè)2.2版本之前的bug    
      if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {    
          System.setProperty("http.keepAlive", "false");    
              }    
      }
    
  • 另外 Gingerbread(2.3) HttpURLConnection 默認(rèn)開啟了 gzip 壓縮,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了請(qǐng)求結(jié)果緩存。再加上 HttpURLConnection 本身 API 相對(duì)簡(jiǎn)單,所以對(duì) Android 來(lái)說(shuō),在 2.3 之后建議使用 HttpURLConnection,之前建議使用 AndroidHttpClient。下面我們來(lái)具體說(shuō)說(shuō)網(wǎng)絡(luò)請(qǐng)求接口HttpStack。

HttpStack接口

HttpStack接口中也只有一個(gè)方法HttpResponse performRequest(Request<?> var1, Map<String, String> var2) throws IOException, AuthFailureError;
在前文我們提到了HttpStack的兩個(gè)實(shí)現(xiàn)類 HttpClientStack 和 HurlStack,在實(shí)現(xiàn)的具體方法中真正進(jìn)行了網(wǎng)絡(luò)請(qǐng)求。

  • HurlStack中封裝了HttpURLConnection

    ...
    URL parsedUrl1 = new URL(url);
    HttpURLConnection connection = this.openConnection(parsedUrl1, request);
    Iterator responseCode = map.keySet().iterator();
    ...

  • HttpClientStack中封裝了HttpClient,也就是傳進(jìn)來(lái)的AndroidHttpClient。

Request的parseNetworkResponse方法

以上步驟,在NetworkDispatcher中收到了NetworkResponse這個(gè)返回值后又會(huì)調(diào)用Request的parseNetworkResponse()方法來(lái)解析NetworkResponse中的數(shù)據(jù),同時(shí)將數(shù)據(jù)寫入到緩存,這個(gè)方法的實(shí)現(xiàn)是交給Request的子類來(lái)完成的,因?yàn)椴煌N類的Request解析的方式也肯定不同。

ResponseDelivery接口

在獲取了解析后的數(shù)據(jù)之后,NetworkDispatcher的run()方法最后調(diào)用了this.mDelivery.postResponse(request, response);將數(shù)據(jù)post到主線程(UI線程),其中的 mDelivery 就是 ResponseDelivery接口的具體實(shí)現(xiàn)類 ExecutorDelivery對(duì)象,具體的方法如下:

@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
    request.markDelivered();
    request.addMarker("post-response");
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

ExecutorDelivery 的構(gòu)造方法如下:

public ExecutorDelivery(final Handler handler) {
    this.mResponsePoster = new Executor() {
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}

在創(chuàng)建其實(shí)例的時(shí)候傳入了 RequestQueue 實(shí)例的 mDelivery :

NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);

而 mDelivery 就是創(chuàng)建 RequestQueue 時(shí)創(chuàng)建的主線程Handler:

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

如此就保證了 ExecutorDelivery 對(duì)象中的 run() 方法在主線程中運(yùn)行。

而在 run() 方法中最核心的代碼就是:

if(this.mResponse.isSuccess()) {
    this.mRequest.deliverResponse(this.mResponse.result);
} else {
    this.mRequest.deliverError(this.mResponse.error);
}

調(diào)用了Request的 deliverResponse(this.mResponse.result)mRequest.deliverError(this.mResponse.error) 方法,其中就調(diào)用了需要重寫的實(shí)現(xiàn)Listener接口方法,將反饋發(fā)送到回調(diào)到UI線程。

這是后來(lái)想起加的一張NetworkDispatcher的工作流程圖:

NetworkDispatcher.png

有什么不對(duì)還請(qǐng)指出,謝謝指教。

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

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

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