Volley 網路請求框架介紹與使用說明

weixin_34007291發表於2018-07-25

一、前言

Volley 是一個基於 HTTP 的網路開源庫,讓 Android 應用更快更容易地連線網路,在 GitHub 上可以找到它的源專案。Volley 具有以下優點:

* 自動排程網路請求。

* 支援多併發網路連線。

* 支援快取。

* 支援請求優先順序。

* 支援取消請求,可以取消單個請求,也可以取消包含多個請求的請求塊。

* 支援自定義。

* 支援非同步資料排序功能。
 
* 支援除錯和具備跟蹤工具。

Volley 適用於 RPC(遠端過程呼叫:Remote Procedure Call)型別操作,例如將搜尋結果頁面作為結構化資料獲取。Volley 可以很容易與所有協議整合,支援原始字串,影象和 JSON。

Volley 不適合大型下載或流媒體操作,因為 Volley 在解析期間將所有響應儲存在記憶體中。對於大型下載操作,可以考慮使用 DownloadManager

最簡單新增 Volley 的方法是將以下依賴項新增到應用程式的 build.gradle 檔案中:

dependencies {
    ...
    compile 'com.android.volley:volley:1.1.1'
}

除此之外,你還可以克隆 Volley 專案庫並將其設定為庫專案:

  1. 通過在命令列鍵入以下內容來克隆專案庫:
git clone https://github.com/google/volley
  1. 將下載的源作為 Android 庫模組匯入到應用專案中。

二、使用 Volley 傳送請求

使用 Volley 之前,必須將 android.permission.INTERNET 許可權新增到應用的清單中。不然,應用無法連線到網路。

1. 使用 newRequestQueue

Volley 提供了一個便捷的方法 Volley.newRequestQueue 為你設定並啟動 RequestQueue 佇列,該 RequestQueue 使用預設值。例如:

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<String>() {
    @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 總是在主執行緒上傳遞已解析的響應,這樣可以很方便地使用接收到的資料填充 UI 控制元件。

2. 傳送請求

要傳送請求,只需構建一個請求並使用 add() 將其新增到 RequestQueue,如上所示。新增請求後,請求將通過網路獲取服務,解析並傳遞其原始響應。

當呼叫 add() 時,Volley 執行一個快取記憶體處理執行緒和一個網路分派執行緒池。當向佇列新增請求時,它會被快取執行緒拾取並進行分類:如果請求可以從快取中獲取服務,則快取響應將在快取執行緒上進行解析,並將解析後的響應在主執行緒上傳遞。如果無法從快取中為請求提供服務,則將其置於網路佇列中。第一個可用的網路執行緒從佇列中獲取請求,執行 HTTP 事務,在子執行緒上解析響應,然後將響應寫入快取,並將解析的響應傳送回主執行緒來進行傳遞。

可以在任意執行緒中新增請求,但響應始終在主執行緒上傳遞。

7182360-41cbad1f8c05c6e0.png
請求的生命週期

3. 取消請求

要取消請求,請對 Request物件呼叫 cancel()。一旦取消,Volley 保證你的響應處理回撥永遠不會被呼叫。一般可以在 Activity 的 onStop() 方法中取消所有待處理的請求。

但是,這樣的話你必須跟蹤所有正在進行的請求。有一種更簡單的方法:你可以使用一個標記物件與每個請求進行關聯。然後,使用此標記物件獲得取消請求的範圍。例如,使用 Activity 將所有由它發出的請求進行標記,並從 onStop() 中呼叫 requestQueue.cancelAll(this)。同樣,在 ViewPager 選項卡中使用各自的選項卡標記所有縮圖影象請求,並在滑動時取消,以確保新選項卡不會被另一個選項卡的請求持有。

以下是使用字串標記的示例:

  1. 定義標記並將其新增到你的請求中。
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);
  1. 在 Activity 的 onStop() 方法中,取消所有具有此標記的請求。
@Override
protected void onStop () {
    super.onStop();
    if (mRequestQueue != null) {
        mRequestQueue.cancelAll(TAG);
    }
}

取消請求時要注意,該請求的響應是否是必要的。

三、設定 RequestQueue

1. 設定網路和快取

RequestQueue 需要兩樣東西都完成它的工作:一個是用於執行請求傳輸的網路,一個是用於處理快取的快取。Volley 工具箱中已經有標準的實現:DiskBasedCache 提供帶有記憶體索引的單檔案響應快取,BasicNetwork 根據你首選的 HTTP 客戶端提供網路傳輸。

BasicNetwork 是 Volley 的預設網路實現。BasicNetwork 必須被用來連線到網路的 HTTP 客戶端初始化。這個客戶端通常是 HttpURLConnection

下面程式碼段顯示了初始化 BasicNetwork 的步驟包括設定 RequestQueue

RequestQueue mRequestQueue;

// Instantiate the cache
Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap

// Set up the network to use HttpURLConnection as the HTTP client.
Network network = new BasicNetwork(new HurlStack());

// Instantiate the RequestQueue with the cache and network.
mRequestQueue = new RequestQueue(cache, network);

// Start the queue
mRequestQueue.start();

String url ="http://www.example.com";

// Formulate the request and handle the response.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
        new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        // Do something with the response
    }
},
    new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            // Handle error
    }
});

// Add the request to the RequestQueue.
mRequestQueue.add(stringRequest);

// ...

你可以在任何時候建立 RequestQueue,並在得到響應後呼叫 stop() 來實現單次請求。

但更常見的情況是建立一個 RequestQueue 的單例,使得它在應用的生命週期內保持執行。

2. 使用單例模式

設定一個 RequestQueue 的單例通常效率最高。推薦的方法是實現封裝 RequestQueue 和其他 Volley 功能的單例類。另一種方法是建立 Application 的子類並在 Application.onCreate() 方法中設定 RequestQueue,但是這種方法並不推薦,因為靜態單例可以以更模組化的方式提供相同的功能。

一個關鍵概念是 RequestQueue 必須使用 Application 上下文進行例項化,而不是 Activity 上下文。這樣可以確保 RequestQueue在應用的生命週期內持續使用,而不是每次重新建立 Activtiy 時重新例項化(例如當使用者旋轉裝置時)。

這裡是一個單類,它提供 RequestQueueImageLoader 功能:

public class MySingleton {
    private static MySingleton mInstance;
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    private static Context mCtx;

    private MySingleton(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();

        mImageLoader = new ImageLoader(mRequestQueue,
                new ImageLoader.ImageCache() {
            private final LruCache<String, Bitmap>
                    cache = new LruCache<String, Bitmap>(20);

            @Override
            public Bitmap getBitmap(String url) {
                return cache.get(url);
            }

            @Override
            public void putBitmap(String url, Bitmap bitmap) {
                cache.put(url, bitmap);
            }
        });
    }

    public static synchronized MySingleton getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new MySingleton(context);
        }
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        getRequestQueue().add(req);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }
}

以下是使用單例類執行 RequestQueue 操作的一些示例:

// Get a RequestQueue
RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
    getRequestQueue();

// ...

// Add a request (in this example, called stringRequest) to your RequestQueue.
MySingleton.getInstance(this).addToRequestQueue(stringRequest);

四、提出標準請求

Volley 支援的常見請求型別:

  • StringRequest。指定 URL 並接收原始字串作為響應。

  • JsonObjectRequest 和 JsonArrayRequest(兩個都是 JsonRequest 的子類)。指定 URL 並分別獲取 JSON 物件或陣列作為響應。

如果你的預期響應是這些型別之一,則不必實現自定義請求。

1. 請求 JSON

Volley 為 JSON 請求提供以下類:

  • JsonArrayRequest - 使用給定的 URL 獲取 JSONArray 響應體。

  • JsonObjectRequest - 使用給定的 URL 獲取 JSONObject 響應體,允許將 JSONObject 作為請求體的一部分傳入。

這兩個類都基於公共基類 JsonRequest。下面程式碼段是提取 JSON 資料並在 UI 中將其顯示為文字:

String url = "http://my-json-feed";

JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
        (Request.Method.GET, url, null, new Response.Listener<JSONObject>() {

    @Override
    public void onResponse(JSONObject response) {
        mTextView.setText("Response: " + response.toString());
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        // TODO: Handle error

    }
});

// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest);

五、實現自定義請求

對於那些不支援開箱即用的資料型別,我們需要實現自定義請求型別。

1. 寫一個自定義請求

大多數請求在工具箱中都有現成的實現,如果響應是字串、影象或 JSON,則不需要實現自定義請求。

對於需要實現自定義請求的情況,你只需執行以下操作:

  • 擴充套件 Request<T> 類,其中 <T> 表示請求所期望的已解析響應的型別。因此,如果解析後的響應是字串,則通過擴充套件 Request<String> 建立自定義請求。

  • 實現抽象方法,parseNetworkResponse()deliverResponse()

1.1 parseNetworkResponse

對於給定型別(例如字串,影象或 JSON),Response 封裝解析的響應用來傳遞。以下是一個示例實現 parseNetworkResponse()

@Override
protected Response<T> parseNetworkResponse(
        NetworkResponse response) {
    try {
        String json = new String(response.data,
        HttpHeaderParser.parseCharset(response.headers));
    return Response.success(gson.fromJson(json, clazz),
    HttpHeaderParser.parseCacheHeaders(response));
    }
    // handle errors
// ...
}

請注意:parseNetworkResponse()NetworkResponse 作為引數 ,其中包含響應有效負載作為 byte []、HTTP 狀態程式碼和響應頭。

自定義實現必須返回一個 Response<T>,其中包含你自定義的響應物件、快取後設資料或錯誤。

如果你的協議具有非標準快取語義,你可以自己構造一個 Cache.Entry,但大多數請求如下使用:

return Response.success(myDecodedObject,
        HttpHeaderParser.parseCacheHeaders(response));

Volley 在工作執行緒中呼叫 parseNetworkResponse()。這確保了耗時的解析操作(例如將 JPEG 解碼為 Bitmap)不會阻塞 UI 執行緒。

1.2 deliverResponse

Volley 在 parseNetworkResponse() 方法中攜帶返回的物件回到主執行緒。大多數請求在此處呼叫回撥介面,例如:

protected void deliverResponse(T response) {
        listener.onResponse(response);

六、示例:GsonRequest

Gson 是一個通過反射將 Java 物件轉換為 JSON 或者將 JSON 轉換為 Java 物件的開源庫。你可以定義擁有相同 JSON 鍵欄位的 Java 物件,將類物件傳給 Gson,Gson 自動使用響應資料填充欄位。

下面是使用 Gson 解析 Volley 請求的完整實現:

public class GsonRequest<T> extends Request<T> {
    private final Gson gson = new Gson();
    private final Class<T> clazz;
    private final Map<String, String> headers;
    private final Listener<T> listener;

    /**
     * Make a GET request and return a parsed object from JSON.
     *
     * @param url URL of the request to make
     * @param clazz Relevant class object, for Gson's reflection
     * @param headers Map of request headers
     */
    public GsonRequest(String url, Class<T> clazz, Map<String, String> headers,
            Listener<T> listener, ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        this.clazz = clazz;
        this.headers = headers;
        this.listener = listener;
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers != null ? headers : super.getHeaders();
    }

    @Override
    protected void deliverResponse(T response) {
        listener.onResponse(response);
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(
                    response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            return Response.success(
                    gson.fromJson(json, clazz),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        }
    }
}

相關文章