天天看點

封裝一個在小項目中使用的volley(通俗簡便、友善靈活)

Google 2013 I/O大會上釋出了Android平台上的網絡通信庫volley,今天Google 2017 I/O大會正在進行中,封裝個小volley做個紀念(主要是以後可以在小項目中直接使用)。

volley的設計目标是非常适合去進行資料量不大,但通信頻繁的網絡操作,而對于大資料量的網絡操作,比如說下載下傳檔案等,volley的表現會非常的糟糕,根據項目的實際情況選擇使用。

volley整體架構類的繼承結構圖:

封裝一個在小項目中使用的volley(通俗簡便、友善靈活)

其實封裝的已經很好了,非常友善我們使用,無論是請求字元串還是圖檔都很簡單。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下載下傳