Google 2013 I/O大會上釋出了Android平台上的網絡通信庫volley,今天Google 2017 I/O大會正在進行中,封裝個小volley做個紀念(主要是以後可以在小項目中直接使用)。
volley的設計目标是非常适合去進行資料量不大,但通信頻繁的網絡操作,而對于大資料量的網絡操作,比如說下載下傳檔案等,volley的表現會非常的糟糕,根據項目的實際情況選擇使用。
volley整體架構類的繼承結構圖:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN0LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9UEVNdXS6hVaWhUYwwmMaZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jM3ITMwMzMwEDOxUDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
其實封裝的已經很好了,非常友善我們使用,無論是請求字元串還是圖檔都很簡單。volley最簡單直接的使用分為三步,這裡以StringRequest為例示範一下:
private void performStringRequest() {
/**
* volley請求的三部曲:
* 1 . 建立請求
* 2 . 建立請求隊列
* 3 . 将請求添加到請求隊列中
*/
String url = "https://www.baidu.com/";
Response.Listener<String> listener = new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 請求成功的結果處理,請求結果在response中
Log.d(TAG, "onResponse: " + response);
}
};
Response.ErrorListener errorListener = new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 請求發生錯誤的回調
Log.d(TAG, "onErrorResponse: " + error.getMessage());
}
};
//get請求:三參數,url , 請求成功監聽和錯誤監聽
StringRequest stringRequest = new StringRequest(url, listener, errorListener);//1 . 建立請求
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());//2 . 建立請求隊列
queue.add(stringRequest);//3 . 将請求添加到請求隊列中
}
上面是StringRequest的get請求,post請求也比較簡單,隻是建立請求的時候指定請求類型,傳遞請求體參數即可,其它步驟同get請求:
new StringRequest(Request.Method.POST, url, listener, errorListener) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> map = new HashMap<String, String>();
map.put("params1", "values1");//傳的參數1
map.put("params2", "values2");//傳的參數2
return map;
}
};//1 . 建立post請求,2,3同get請求(略)
這隻是以StringRequest為例簡單介紹了volley的使用,其它幾個兄弟類的使用方式與其類似,這裡就不一一舉例了。
每次這麼做代碼量會比較多,而且每做一次請求就建立一個請求隊列RequestQueue明顯是不合理的,接下來稍微封裝一下,方面日後小項目的使用:
模仿StringRequest封裝一個自己的Request請求類,可以直接進行get或者post請求
public class QJRequest<T> extends Request<T> {
private Class<T> mClass;
private Gson mGson;
private Response.Listener<T> mListener;
private Map<String, String> requestBodyMap;//post 請求 請求體參數集合
/**
* POST 請求
*
* @param clazz 請求結果要解析成的java bean
* @param url
* @param map 請求體參數
* @param listener
* @param errorListener
*/
public QJRequest(Class<T> clazz, String url, Map<String, String> map, Response.Listener listener, Response.ErrorListener errorListener) {
super(Method.POST, url, errorListener);
mClass = clazz;
mListener = listener;//模仿StringRequest的做法
requestBodyMap = map;
}
/**
* GET 請求
*
* @param url 請求的url
* @param listener 成功的監聽
* @param errorListener 失敗的監聽
*/
public QJRequest(Class<T> clazz, String url, Response.Listener listener, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
mClass = clazz;
mListener = listener;//模仿StringRequest的做法
}
@Override
protected Response parseNetworkResponse(NetworkResponse networkResponse) {
byte[] data = networkResponse.data;//請求成功後得到的byte數組
// 使用 Gson解析
try {
String result = new String(data, "utf-8");//使用utf-8防止亂碼問題
T bean = getGson().fromJson(result, mClass);
//傳回解析後的結果
//第一個參數是解析後的結果(java bean)
//第二個參數是緩存的條目資訊
return Response.success(bean, HttpHeaderParser.parseCacheHeaders(networkResponse));
} catch (IOException e) {
e.printStackTrace();
return Response.error(new ParseError(networkResponse));//傳回解析錯誤
}
}
@Override
protected void deliverResponse(T t) {
this.mListener.onResponse(t);
}
/**
* @return post 請求的請求體參數
* @throws AuthFailureError
*/
@Override
protected Map<String, String> getParams() throws AuthFailureError {
return requestBodyMap;
}
/**
* @return 一個簡單的單例 Gson對象
*/
private Gson getGson() {
if (mGson == null) {
mGson = new Gson();
}
return mGson;
}
/**
* 将請求加入到請求隊列中
* 建立請求對象之後可以直接調用此方法,鍊式調用友善代碼書寫
*/
public void execute() {
NetworkManager.addRequest(this);
}
}
請求對象我們有了,還需要一個請求隊列,我們把這個請求隊列封裝在網絡管理類中
public class NetworkManager {
//整個app隻保留和維護一個Volley請求隊列
private static RequestQueue mRequestQueue;
//緩存大小是我們運作記憶體大小的1/4
private static final int CACHE_SIZE = (int) (Runtime.getRuntime().freeMemory() / 4);
//維護一個全局的ImageLoader
private static ImageLoader mImageLoader;//Helper that handles loading and caching images from remote URLs.
/**
* 在application 的onCreate方法中調用此方法
*
* @param context getApplicationContext
*/
public static void init(Context context) {
mRequestQueue = Volley.newRequestQueue(context);
mImageLoader = new ImageLoader(mRequestQueue, new MyImageCache(CACHE_SIZE));
}
/**
* 添加請求到請求隊列
*/
public static void addRequest(Request request) {
mRequestQueue.add(request);
}
/**
* LruCache (Least Recent Use Cache)
*
* 當緩存空間已經滿了,就會把最近最少使用的資料清除掉
*
* K 存儲資料的鍵值
* V 存儲的資料
*/
public static class MyImageCache extends LruCache<String, Bitmap> implements ImageLoader.ImageCache {
/**
* @param maxSize 緩存的大小
*/
public MyImageCache(int maxSize) {
super(maxSize);
}
/**
* @return 傳回對應資料緩存的大小, 即圖檔的大小
*/
@Override
protected int sizeOf(String key, Bitmap value) {
//return value.getByteCount(); api 要求比較高 , 故用下面的 , 實質相同
return value.getRowBytes() * value.getHeight();
}
/**
* 從緩存裡面擷取對應url的圖檔
*
* @param url 緩存的key
*/
@Override
public Bitmap getBitmap(String url) {
return get(url);//從lru cache裡面擷取圖檔
}
/**
* 把對應url的圖檔存進緩存
*/
@Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
/**
* @return 全局唯一的ImageLoader
*/
public static ImageLoader getImageLoader() {
return mImageLoader;
}
}
在應用的Application中還需要初始化我們的NetWorkManager:
public class QJApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
NetworkManager.init(getApplicationContext());
}
}
如此簡單封裝之後我們的請求就可以這麼寫了:
private void performGetRequest() {
String url = "http://192.168.1.9:8080/version.json";//自己測試的url
Response.Listener<VersionBean> listener = new Response.Listener<VersionBean>() {
@Override
public void onResponse(VersionBean o) {
Log.d(TAG, "onResponse: " + o);
}
};
Response.ErrorListener errorListener = new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
Log.d(TAG, "onErrorResponse: " + volleyError.getMessage());
}
};
new QJRequest(VersionBean.class, url, listener, errorListener).execute();//get請求封裝後寫法示例
}
傳入url和成功錯誤的回調之後我們最後可以得到的是對應json資料的java bean類,這樣就友善很多了,post請求亦是如此
private void performPostRequest() {
String url = "http://139.199.76.41:8080/vr-app/game/gameRecommends";
Map<String, String> map = new HashMap<>();
map.put("item", "2");
Response.Listener<GameRecommendsBean> listener = new Response.Listener<GameRecommendsBean>() {
@Override
public void onResponse(GameRecommendsBean o) {
Log.d(TAG, "onResponse: " + o);
}
};
Response.ErrorListener errorListener = new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
Log.d(TAG, "onErrorResponse: " + volleyError.getMessage());
}
};
new QJRequest(GameRecommendsBean.class, url, map, listener, errorListener).execute();
}
與get請求相比,post請求隻是多了一個請求體參數,需要一個Map集合,建立請求之後調用execute()方法直接發送請求,便于代碼的書寫
對于圖檔的請求,volley給我提供了一個用于請求網絡圖檔的ImageView,即NetworkImageView,利用我們的簡單封裝可以很友善的請求圖檔
mNetworkImageView.setImageUrl(url, NetworkManager.getImageLoader());//使用NetWorkImageView隻需要url即可
簡單封裝到此結束,謝謝閱讀!
封裝及使用Demo下載下傳
---------------------------------------------2017/12/22---------------------------------------------
對于上面代碼的封裝中有幾處後來優化的地方做如下說明:
1. 緩存大小的設定(NetworkManager中的init方法)
CACHE_SIZE = (int) (Runtime.getRuntime().maxMemory() >> 3);
if (CACHE_SIZE <= 0) {
CACHE_SIZE = 10240000;//未擷取到jvm的大小,則設定為固定值,将近 10M
}
2. 增加加載網絡圖檔的方法(NetworkManager類中)
/**
* 加載網絡圖檔,将網絡圖檔顯示到對應的imageview上
*
* @param url 網絡圖檔的位址
* @param imageView 顯示網絡圖檔的iamgeview,該imageview是android系統的,不能用 volley中的 NetworkImageView
* @param defaultImageResId
* @param errorImageResId
*/
public static void displayImage(String url, ImageView imageView, int defaultImageResId, int errorImageResId) {
mImageLoader.get(url, ImageLoader.getImageListener(imageView, defaultImageResId, errorImageResId));
}
3. 避免Gson對象重複建立(QJRequest類中)
private Gson mGson;//優化前
private static Gson mGson;//優化後
優化之前每次建立QJRequest對象都會建立一個Gson對象,優化是為了共用一個Gson對象
優化後的Volley封裝及使用Demo下載下傳