網絡請求庫Volley詳解

這篇文章是系列文章An Introduction to Volley的一部分,下一篇文章是:Creating a Weather Application for Mars Using Volley


Volley是谷歌2013年在I/O大會期間推出的網絡庫。開發Volley是因爲在Android SDK中缺乏一個用戶體驗良好的網絡加載類。

在Volley發佈之前,開發具有客戶端與服務端交互程序的唯一工具是標準的java類java.net.HttpURLConnection以及Apache 的 org.apache.http.client。

除開這兩個工具的bug不說,所有http鏈接之外的事情都必須完全自己寫,如果你想緩存圖片或者發送一個請求,你必須從零開始開發。

幸運的是,我們現在有了Volley,完全填補了我們的這些需求。

1. 爲什麼是Volley?

避開HttpUrlConnection 和HttpClient

在較低的api版本中(多數是在Gingerbread和Froyo中),HttpUrlConnection和HttpClient 遠未達到完美。有一些已知的問題  和bugs 一直未被修復,HttpClient 自上次api更新(API 22)之後就已經過時,意味着不再維護,後續可能也會被移除。

這就是需要轉到一個更穩定的處理網絡請求的方法的主要原因。

也爲了避開AsyncTask

自從引入Honeycomb(API 11)以來,系統強制網絡操作必須運行在單獨的線程中,UI線程之外。這個重大的變化導致AsyncTask<Params, Progress, Result> 的大量使用。

要使用AsyncTask,首先你得在onPreExecute做一些準備工作,比如定義context。然後在doInBackground 方法中執行後臺任務,最後在onPostExecute中處理結果。這要比實現service更簡單直接,因此有大量的例子和文章出現。

但是AsyncTask的主要問題是調用的順序。你無法決定哪個請求走在最前面,哪個請求必須等待,所有的請求都是按照先進先出的順序,FIFO。

在某些情況下,問題就出來了,比如當你需要加載一個item中帶有圖片的列表。當用戶向下滾動想獲得新的數據時,你無法告訴Activity先加載下一頁的json數據,只有慢慢等待上一頁的圖片加載完。對於像facebook和Twiitter這種item數據的重要性遠大於相關圖片的應用來說,這會導致嚴重的用戶體驗問題。

Volley用其強大的cancellation api解決了這個問題。當調用的時候,你不再需要在onPostExecute中檢查Activity是否被銷燬。這幫助我們避免了不想要的NullPointerException。

它速度更快

前些時候,Google+團隊針對每種可以使用的網絡請求方式做了一些列的性能測試。當應用在RESTful 風格的應用中時,Volley的得分比其他候選方法高近10倍。

它可以緩存所有東西

Volley可以自動緩存請求,這點確實是關乎生死的問題。讓我們暫時回到上面所提到的那個例子。你有一個item的列表 -  假設是JSON 數組 -  並且每個item都包括一段描述和一個縮略圖。現在設想一下用戶旋轉屏幕之後會發生的事情:activity銷燬,列表重新加載,同樣的還有圖片。長話短說就是:嚴重的資源浪費以及糟糕的用戶體驗。

事實證明,Volley在克服這種困難中是相當有用的。它可以記住前一個調用同時處理Activity的銷燬與重建。它緩存所有的東西,這點你也不用擔心。

Small Metadata Operations

Volley在輕量級的調用中堪稱完美,比如請求JSON對象,或者列表的一部分,或者被選中item的詳情等等。它是爲RESTful應用設計的,在這種特定場景下可以發揮自己最好的優勢。

但是當把它應用在數據流或者大文件下載中,就不是那麼好了。這就是爲什麼它叫Volley(萬箭齊發),而不是叫加農炮。

2. 內部架構

Volley分爲三層,每一層都工作在自己的線程中。

underthehood.jpg

主線程

在主線程中,你只允許觸發請求與處理返回結果,不能多也不能少。

其結果就是你實際上可以忽略在使用AsyncTask的時候doInBackground 方法裏面所做的事情。Volley 自動管理http傳輸同時捕獲網絡錯誤,這些都是以前需要我們自己考慮的。

緩存與網絡線程

當你向隊列中添加了一個請求,背後發生了幾件事情。Volley會檢查這個請求是否可以從緩存中得到。如果可以,緩存將負責讀取,解析,和分發。否則將傳遞給網絡線程。

在網絡線程中,一些列的輪詢線程不斷的在工作。第一個可用的網絡線程線程讓請求隊列出列,發出http請求,解析返回的結果,並寫入到緩存中。最後,把解析的結果分發給主線程的listener中。

3. 開始

第一步: 導入Volley

Volley設置起來並不是很方便,貌似沒有官方的Maven repository,這讓人理解不能。你必須依賴官方的源代碼。

第一件事就是,從它的repository 下載Volley源碼。這個很簡單,下面的Git命令可以爲你做完所有的工作:

1
git clone https://android.googlesource.com/platform/frameworks/volley

幾周之前,你還可以使用ant命令行(android update project -p . 然後 ant jar)將所有的東西都打包成jar,然後直接在Android Studio項目中使用compile files('libs/volley.jar')來導入jar。


但是最近,google更新了Volley成Android Studio的構建風格,讓它很難創建一個標準的jar,你仍然可以這樣做,但是隻能使用老版本的庫,雖然這種方式也許是最快速的的,但個人不推薦你使用這種方式。

你應該使用經典的方式來設置Volley,將源碼作爲一個module導入。在Android Studio中,在打開項目的情況下,選擇File > New Module,然後選擇Import Existing Project。選擇你下載的源碼的所在目錄然後確認。一個名爲Volley的文件夾將出現在你的項目結構中。Android Studio會自動的更新settings.gradle文件以包含Volley module,因此你只需添加你的依賴compile project(':volley'),然後就完成了。

不過還有第三種方法,你可以在build.gradle 文件的依賴部分添加這行代碼:

1
compile 'com.mcxiaoke.volley:library-aar:1.0.15'

這是谷歌官方repository的一個鏡像,通常會同步更新。這可能是最簡單快速的方法,但是記住,這不是官方的Maven repository,沒有保證,不被谷歌支持。

在我看來,最好還是多花幾分鐘導入官方的代碼比較好。這樣你就可以輕鬆的跳到原始定義與實現的代碼中,如果需要,你還可以修改Volley。

第二步: 使用Volley

通常Volley只會用到兩個類RequestQueue 和Request,你首先創建一個RequestQueue,RequestQueue管理工作線程並將解析的結果發送給主線程。然後你傳遞一個或者多個Request 
對象給他。

Request 的構造函數的參數總是包含類型(GET, POST, 等等),數據源的url,以及事件監聽者。根據請求類型的不同,可能還需要一些其他的參數。

在接下來的例子中,我通過Volley.newRequestQueue方法創建了一個RequestQueue 對象,使用Volley定義的默認值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
String url = "http://httpbin.org/html";
  
// Request a string response
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
  
        // Result handling 
        System.out.println(response.substring(0,100));
  
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
          
        // Error handling
        System.out.println("Something went wrong!");
        error.printStackTrace();
  
    }
});
  
// Add the request to the queue
Volley.newRequestQueue(this).add(stringRequest);

正如你看到的那樣,這非常直接:創建一個請求,然後將它添加到請求隊列,完成。

注意listener的語法類似AsyncTask.onPostExecute,只是變成了onResponse。這並不是巧合。Volley 的開發者故意將庫的api做的和AsyncTask 方法類似。這樣從AsyncTask轉到Volley就會輕鬆很多。

如果你需要在幾個Activity中觸發多個請求,你應該避免使用上面的方法Volley.newRequestQueue.add。在整個項目中實例化一個共享的請求隊列會更好些。:

MySingletonClass.getInstance().getRequestQueue().add(myRequest);

我們這個系列的下一篇教程中我們將看到這種用法。

4. 動手實踐

處理標準的請求 

Volley實現了三種常見的請求類型:

  • StringRequest

  • ImageRequest

  • JsonRequest

每個類都是繼承自前面使用了的Request類。我們已經在上個例子中使用了StringRequest ,下面讓我們來看看JsonRequest是如何工作的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  
JsonObjectRequest jsonRequest = new JsonObjectRequest
        (Request.Method.GET, url, nullnew Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                // the response is already constructed as a JSONObject!
                try {
                    response = response.getJSONObject("args");
                    String site = response.getString("site"),
                            network = response.getString("network");
                    System.out.println("Site: "+site+"\nNetwork: "+network);
                catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, new Response.ErrorListener() {
  
            @Override
            public void onErrorResponse(VolleyError error) {
                error.printStackTrace();
            }
        });
  
Volley.newRequestQueue(this).add(jsonRequest);

很漂亮是吧?你可以看到,返回結果的類型已經是JSONObject的了。如果你需要,你還可以請求JSONArray ,只需用JsonArrayRequest 替代aJsonObjectRequest就可以了。

跟之前一樣,構造函數的第一個參數是使用的http方法,然後提供獲取json的url參數,第三個參數是null,表示請求url不需要帶其他請求參數。最後是用於接收返回JSON的listener以及錯誤listener。如果你想忽略錯誤,可以爲null。

獲取圖片則需要更多的工作。有三種請求圖片的方法。ImageRequest 是標準方法。通過提供的Url,她將你請求的圖片顯示在一個普通的ImageView中。壓縮與大小調整的操作都發生在工作線程中。第二種選擇是ImageLoader 
類。你可以將之想象成數量龐大的ImageRequests,比如生成一個帶有圖片的ListView。第三種選擇是NetworkImageView,

下面來看一個例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
String url = "http://i.imgur.com/Nwk25LA.jpg";
mImageView = (ImageView) findViewById(R.id.image);
  
ImageRequest imgRequest = new ImageRequest(url,
        new Response.Listener<Bitmap>() {
    @Override
    public void onResponse(Bitmap response) {
        mImageView.setImageBitmap(response);
    }
}, 0, 0, ImageView.ScaleType.FIT_XY, Bitmap.Config.ARGB_8888, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mImageView.setBackgroundColor(Color.parseColor("#ff0000"));
        error.printStackTrace();
    }
});
  
Volley.newRequestQueue(this).add(imgRequest);

第一個參數是圖片的url,第二個是結果的listener,第三、第四個參數是maxWidth(最大寬度) 和 maxHeight(最大高度),你可以設置爲0來忽略他們。然後是用於計算圖片所需大小的ScaleType,然後是用於指定圖片壓縮方式的參數,我建議總是使用 Bitmap.Config.ARGB_8888,最後是一個錯誤listener。

注意Volley默認會將這種請求的優先級設置爲low。

1
2
3
4
5
6
// Snippet taken from ImageRequest.java, 
// in the Volley source code
@Override
public Priority getPriority() {
    return Priority.LOW;
}

注:因爲是請求圖片,所以沒有http類型參數,圖片請求總是get的。

 POST 請求

從get請求切換到post請求是非常簡單的。你只需要在request的構造方法中改變Request.Method,同時重寫getParams方法,返回包含請求參數的Map<String, String>。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
String url = "http://httpbin.org/post";
  
StringRequest postRequest = new StringRequest(Request.Method.POST, url,
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                try {
                    JSONObject jsonResponse = new JSONObject(response).getJSONObject("form");
                    String site = jsonResponse.getString("site"),
                            network = jsonResponse.getString("network");
                    System.out.println("Site: "+site+"\nNetwork: "+network);
                catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        },
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                error.printStackTrace();
            }
        }
) {
    @Override
    protected Map<String, String> getParams()
    {
        Map<String, String>  params = new HashMap<>();
        // the POST parameters:
        params.put("site""code");
        params.put("network""tutsplus");
        return params;
    }
};
Volley.newRequestQueue(this).add(postRequest);

取消一個請求

如果你想取消所有的請求,在onStop方法中添加如下代碼:

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onStop() {
    super.onStop();
    mRequestQueue.cancelAll(new RequestQueue.RequestFilter() {
        @Override
        public boolean apply(Request<?> request) {
            // do I have to cancel this?
            return true// -> always yes
        }
    });
}

這樣你就不必擔心在onResponse被調用的時候用戶已經銷燬Activity。這種情況下會拋出NullPointerException異。但是post請求則需要繼續,即使用戶已經改變了Activity。我們可以通過使用tag來做到,在構造GET請求的時候,添加一個tag給它。

1
2
3
// after declaring your request
request.setTag("GET");
mRequestQueue.add(request);

如果要取消GET請求,只需簡單的添加下面的一行代碼:

1
mRequestQueue.cancelAll("GET");

這樣你就只會取消GET請求,讓其它請求不受影響。注意你必須手動在銷燬的Activity中處理這種情況。

管理Cookies和Request優先級

Volley doesn't provide a method for setting the cookies of a request, nor its priority. It probably will in the future, since it's a serious omission. For the time being, however, you have to extend the Request class.

Volley並沒有提供設置一個請求的cookies以及優先級的方法。也許在將來會有,畢竟這是一個很嚴重的疏忽。但是目前,你需要繼承Request類。

對於cookies的管理,你需要添加請求的header,重寫getHeaders方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class CustomRequest extends JsonObjectRequest {
  
    // Since we're extending a Request class
    // we just use its constructor
    public CustomRequest(int method, String url, JSONObject jsonRequest,
                         Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) {
        super(method, url, jsonRequest, listener, errorListener);
    }
  
    private Map<String, String> headers = new HashMap<>();
  
    /**
     * Custom class!
     */
    public void setCookies(List<String> cookies) {
        StringBuilder sb = new StringBuilder();
        for (String cookie : cookies) {
            sb.append(cookie).append("; ");
        }
        headers.put("Cookie", sb.toString());
    }
  
    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers;
    }
      
}

有了上面的代碼,你就可以直接使用setCookies方法爲請求提供cookie列表了。

1
2
3
4
5
6
7
8
9
10
11
12
// Firstly, you create the list of the cookies,
// conformed to the HTTP conventions
// i.e. key=value
List<String> cookies = new ArrayList<>();
cookies.add("site=code");
cookies.add("network=tutsplus");
  
// then you invoke your custom method
customRequest.setCookies(cookies);
  
// and finally add the request to the queue
Volley.newRequestQueue(this).add(customRequest);

而對於優先級,你同樣需要繼承Request類,重寫getPriority方法。下面是代碼的樣子:

1
2
3
4
5
6
7
8
9
10
11
12
Priority mPriority;
  
public void setPriority(Priority priority) {
    mPriority = priority;
}
  
@Override
public Priority getPriority() {
    // If you didn't use the setPriority method,
    // the priority is automatically set to NORMAL
    return mPriority != null ? mPriority : Priority.NORMAL;
}

然後,在主線程中,調用下面的一行代碼來設置請求的優先級:

1
customRequest.setPriority(Priority.HIGH);

你可以從如下的優先級中選擇一個:

1
2
3
4
Priority.LOW // images, thumbnails, ...
Priority.NORMAL // residual
Priority.HIGH // descriptions, lists, ...
Priority.IMMEDIATE // login, logout, ...

總結

在這篇文章中,我們看到了Volley是如何工作的。我們首先看到了爲什麼使用Volley而不是Android SDK中自帶的其他解決方案。然後我們深入到庫的內部,查看它的工作流程以及支持的請求類型。最後,我們動手創建了幾個簡單的Request,並實現了一個可以設置優先級與cookies的自定義Request。


在下一部分中,我們將創建一個使用了Volley的簡單應用。向你演示如何使用探索者號在火星上收集的天氣數據來製作一個火星天氣app。

發佈了62 篇原創文章 · 獲贊 7 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章