Volley的使用以及源碼解析

1. 功能介紹

1.1. Volley
Volley 是 Google 推出的 Android 異步網絡請求框架和圖片加載框架。在 Google I/O 2013 大會上發佈。
谷歌大會配圖
從名字由來和配圖中無數急促的火箭可以看出 Volley 的特點:特別適合數據量小,通信頻繁的網絡操作。(個人認爲 Android 應用中絕大多數的網絡操作都屬於這種類型)。

1.2 Volley 的主要特點

(1). 擴展性強。Volley 中大多是基於接口的設計,可配置性強。
(2). 一定程度符合 Http 規範,包括返回 ResponseCode(2xx、3xx、4xx、5xx)的處理,請求頭的處理,緩存機制的支持等。並支持重試及優先級定義。
(3). 默認 Android2.3 及以上基於 HttpURLConnection,2.3 以下基於 HttpClient 實現,這兩者的區別及優劣在4.2.1 Volley中具體介紹。
(4). 提供簡便的圖片加載工具。

1.3 Google公司爲什麼會去搞一個volley框架?
1.用戶開啓一個activity,然後加載網絡,這個時候.如果用戶點擊了finish按鈕.activity被銷燬了–>網絡請求和activity的生命週期是應該聯動起來的.
2.listview加載圖片的情況比較多.如果用戶快速的去滑動listview–>getView->快速的加載圖片,用戶停止操作的時候.其實真正現實的圖片最多就幾張—>圖片應該緩存起來(內存 +本地 )
3.如果用戶打開了一個activity,用戶旋轉了一下屏幕.activity會旋轉–>生命週期重走了–>網絡請求緩存
4.之前我們的網絡請求,httpurlconnection,httpclient,asynctask(api)–>android sdk–>封裝性不夠好.1000個開發者就有1000種使用方式–>不夠統一
5.理念很容易理解,是開源的.

2. 總體設計

2.1 總體設計圖
這裏寫圖片描述

上面是 Volley 的總體設計圖,主要是通過兩種Dispatch Thread不斷從RequestQueue中取出請求,根據是否已緩存調用Cache或Network這兩類數據獲取接口之一,從內存緩存或是服務器取得請求的數據,然後交由ResponseDelivery去做結果分發及回調處理。

2.2 Volley 中的概念
Volley 的調用比較簡單,通過 newRequestQueue(…) 函數新建並啓動一個請求隊列RequestQueue後,只需要往這個RequestQueue不斷 add Request 即可。
Volley:Volley 對外暴露的 API,通過 newRequestQueue(…) 函數新建並啓動一個請求隊列RequestQueue。
Request:表示一個請求的抽象類。StringRequest、JsonRequest、ImageRequest 都是它的子類,表示某種類型的請求。
RequestQueue:表示請求隊列,裏面包含一個CacheDispatcher(用於處理走緩存請求的調度線程)、NetworkDispatcher數組(用於處理走網絡請求的調度線程),一個ResponseDelivery(返回結果分發接口),通過 start() 函數啓動時會啓動CacheDispatcher和NetworkDispatchers。
CacheDispatcher:一個線程,用於調度處理走緩存的請求。啓動後會不斷從緩存請求隊列中取請求處理,隊列爲空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理。當結果未緩存過、緩存失效或緩存需要刷新的情況下,該請求都需要重新進入NetworkDispatcher去調度處理。
NetworkDispatcher:一個線程,用於調度處理走網絡的請求。啓動後會不斷從網絡請求隊列中取請求處理,隊列爲空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理,並判斷結果是否要進行緩存。
ResponseDelivery:返回結果分發接口,目前只有基於ExecutorDelivery的在入參 handler 對應線程內進行分發。
HttpStack:處理 Http 請求,返回請求結果。目前 Volley 中有基於 HttpURLConnection 的HurlStack和 基於 Apache HttpClient 的HttpClientStack。
Network:調用HttpStack處理請求,並將結果轉換爲可被ResponseDelivery處理的NetworkResponse。
Cache:緩存請求結果,Volley 默認使用的是基於 sdcard 的DiskBasedCache。NetworkDispatcher得到請求結果後判斷是否需要存儲在 Cache,CacheDispatcher會從 Cache 中取緩存結果。

3. 流程圖

Volley 請求流程圖
這裏寫圖片描述

4. 詳細設計

4.1 類關係圖
這裏寫圖片描述
這是 Volley 框架的主要類關係圖
圖中紅色圈內的部分,組成了 Volley 框架的核心,圍繞 RequestQueue 類,將各個功能點以組合的方式結合在了一起。各個功能點也都是以接口或者抽象類的形式提供。
紅色圈外面的部分,在 Volley 源碼中放在了 toolbox 包中,作爲 Volley 爲各個功能點提供的默認的具體實現。
通過類圖我們看出, Volley 有着非常好的拓展性。通過各個功能點的接口,我們可以給出自定義的,更符合我們需求的具體實現。
多用組合,少用繼承;針對接口編程,不針對具體實現編程。
優秀框架的設計,令人叫絕,受益良多。

5.Volley兩個核心類

  • Request:一個請求
    • StringRequest:請求的時候直接回來一個String 文本信息 —>url–>jsonString
    • JsonObjectRequest:請求的時候直接回來一個JsonObject —> url–>JsonObject
    • JsonArrayRequest:請求的時候直接回來一個JsonArray —> url–>JsonArray
    • ImageRequest:請求的時候直接回來一個Bitmap
    • 自定義請求:一會我們會結合gson 自定義請求 GsonRequest–>Bean
  • ImageLoader:圖片的加載器
  • NetWorkImageView:繼承了imageView,對ImageView進行了拓展
  • RequestQueue:請求隊列.

測試主要代碼如下

public class MainActivity extends Activity {

    private ImageView           mIv;
    private NetworkImageView    mNiv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mIv = (ImageView) findViewById(R.id.iv);
        mNiv = (NetworkImageView) findViewById(R.id.niv);
        initStringRequest();
        initJsonObjectRequest();// url-->JsonObject-->Bean
        initJsonArrayRequest();
        initImageRequest();
        // initNetWorkImageView();
        initImageLoader();
        initGsonRequest();
    }

    private void initGsonRequest() {
        findViewById(R.id.btn6).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // 1.
                String url = "http://188.188.6.100:8080/json/ip.json";
                GsonRequest<Ip> gsonRequest = new GsonRequest<Ip>(url, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        System.out.println("error:" + error.getMessage());
                    }
                }, Ip.class, new Listener<Ip>() {

                    @Override
                    public void onResponse(Ip ip) {
                        System.out.println(ip.origin);
                    }
                });
                /*// 2.
                RequestQueue queue = Volley.newRequestQueue(MainActivity.this);
                // 3.
                queue.add(gsonRequest);*/
                VolleyTools.getInstance(MainActivity.this).getQueue().add(gsonRequest);
            }
        });
    }

    private void initImageLoader() {
        findViewById(R.id.btn5).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                RequestQueue queue = Volley.newRequestQueue(MainActivity.this);
                ImageCache imageCache = new LruMemImageCache();
                ImageLoader imageLoader = new ImageLoader(queue, imageCache);
                String requestUrl = "http://188.188.6.100:8080/img/3812b31bb051f81901e3b44dd8b44aed2f73e7f7.jpg";
                imageLoader.get(requestUrl, new ImageListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        System.out.println("error:" + error.getMessage());
                    }

                    @Override
                    public void onResponse(ImageContainer response, boolean isImmediate) {
                        Bitmap bitmap = response.getBitmap();
                        mIv.setImageBitmap(bitmap);
                    }
                }, 0, 0);

            }
        });
    }

    private void initNetWorkImageView() {
        mNiv.setDefaultImageResId(R.drawable.ic_launcher);
        mNiv.setErrorImageResId(R.drawable.ic_launcher_error);
        String url = "http://188.188.6.100:8080/img/3812b31bb051f81901e3b44dd8b44aed2f73e7f7.jpg";
        RequestQueue queue = Volley.newRequestQueue(MainActivity.this);
        /**
         * volley裏面沒有內存以及磁盤緩存,只是提供了圖片緩存的核心操作(存,取)
         */

        ImageCache imageCache = new LruMemImageCache();
        ImageLoader imageLoader = new ImageLoader(queue, imageCache);
        // ImageLoader imageLoader = new ImageLoader(請求對象, imageCache接口對象);
        mNiv.setImageUrl(url, imageLoader);
    }

    private void initImageRequest() {
        findViewById(R.id.btn4).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                /**
                 maxWidth Maximum:是0,就是原樣輸出,非0,就是按照指定的像素值輸出了吧
                 maxHeight Maximum :是0,就是原樣輸出,非0,就是按照指定的像素值輸出了吧
                  幫我們進行了大圖片的處理
                 */
                // 1.
                String url = "http://188.188.6.100:8080/img/3812b31bb051f81901e3b44dd8b44aed2f73e7f7.jpg";
                ImageRequest imageRequest = new ImageRequest(url, new Listener<Bitmap>() {

                    @Override
                    public void onResponse(Bitmap bitmap) {
                        mIv.setImageBitmap(bitmap);
                    }
                }, 200, 100, Config.ARGB_4444, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        System.out.println("error:" + error.getMessage());
                    }
                });
                // 2.
                RequestQueue queue = Volley.newRequestQueue(MainActivity.this);
                // 3.
                queue.add(imageRequest);
            }
        });
    }

    private void initJsonArrayRequest() {
        findViewById(R.id.btn3).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // 1.創建請求對象
                String url = "http://188.188.6.100:8080/json/jsonArray.json";
                JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(url, new Listener<JSONArray>() {

                    @Override
                    public void onResponse(JSONArray response) {
                        System.out.println("success:" + response.length());
                    }
                }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        System.out.println("error:" + error.getMessage());
                    }
                });
                // 2.創建隊列
                RequestQueue queue = Volley.newRequestQueue(MainActivity.this);
                // 3.發起請求
                queue.add(jsonArrayRequest);
            }
        });
    }

    private void initJsonObjectRequest() {
        findViewById(R.id.btn2).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // 1. 創建相應的請求對象
                String url = "http://188.188.6.100:8080/json/ip.json";
                JSONObject jsonRequest = null;// 請求參數可以通過jsonObject傳遞
                JsonObjectRequest jsonObjectRequest =
                        new JsonObjectRequest(url, jsonRequest, new Listener<JSONObject>() {

                            @Override
                            public void onResponse(JSONObject response) {
                                // jsonString-->JSONObject
                                System.out.println("success:" + response.optString("origin"));
                            }
                        }, new ErrorListener() {

                            @Override
                            public void onErrorResponse(VolleyError error) {
                                System.out.println("error:" + error.getMessage());
                            }
                        });
                // 2.創建請求隊列
                RequestQueue queue = Volley.newRequestQueue(MainActivity.this);
                // 3.請求對象放入到請求隊列中
                queue.add(jsonObjectRequest);
            }
        });
    }

    private void initStringRequest() {
        findViewById(R.id.btn1).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                String url = "http://www.baidu.com";
                // 1.創建相應的 請求對象
                StringRequest stringRequest = new StringRequest(url, new Listener<String>() {

                    @Override
                    public void onResponse(String response) {
                        System.out.println("success:" + response);
                    }
                }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        System.out.println("error:" + error.getMessage());
                    }
                });
                // 2.創建 請求隊列
                RequestQueue queue = Volley.newRequestQueue(MainActivity.this);
                // 3.發起請求 請求對象,放到請求隊列
                queue.add(stringRequest);
            }
        });
    }
}

佈局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="StringRequest" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="JsonObjectRequest" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="JsonArrayRequest" />

    <Button
        android:id="@+id/btn4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ImageRequest" />

    <Button
        android:id="@+id/btn5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ImageLoader" />
    <Button
        android:id="@+id/btn6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="GsonRequest" />

    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/niv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

自定義GsonRequest請求成功之後返回JavaBean

public class GsonRequest<T> extends Request<T> {

    private Class<T>    clazz;

    private Listener<T> mListener;

    public GsonRequest(String url, ErrorListener errorListener, Class<T> clazz, Listener<T> listener) {
        super(url, errorListener);
        this.clazz = clazz;
        this.mListener = listener;

    }

    /**
     * 解析響應的數據
     */
    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        String jsonString;
        try {
            jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            jsonString = new String(response.data);
        }
        // jsonString-->Bean
        T t;
        try {
            Gson gson = new Gson();
            t = gson.fromJson(jsonString, clazz);
            return Response.success(t, HttpHeaderParser.parseCacheHeaders(response));
        } catch (JsonSyntaxException e) {
            e.printStackTrace();
            return Response.error(new ParseError());
        }
    }

    /**
     * 傳遞結果
     */
    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }

}

由於Volley沒有實現內存緩存與磁盤緩存只提供了接口,需要我們自己實現(內存緩存代碼如下<根據LruCache緩存策略>)

public class LruMemImageCache implements ImageCache {
    private LruCache<String, Bitmap>    mLruCache;
    // 1.定義內存緩存的最大值
    // private int maxSize = 4; // 內存緩存的最大值 4m
    private int                         maxSize = 4 * 1024 * 1024;  // 內存緩存的最大值 4194304 byte

    public LruMemImageCache() {
        super();
        mLruCache = new LruCache<String, Bitmap>(maxSize) {
            // 2.覆寫sizeOf方法
            /**
             * sizeOf方法是做啥的?
             *   1.返回的是放到LinkedHashMap中具體實體的大小
             *   2.統一和maxSize定義的單位
             */
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // return bitmap.getByteCount() / 1024 / 1024;//每個圖片是多少m
                return bitmap.getByteCount();// byte//每個圖片的是多少byte
                // return super.sizeOf(key, value);
            }
        };
    }

    /**
     * 取的操作
     */
    @Override
    public Bitmap getBitmap(String url) {
        Bitmap bitmap = mLruCache.get(url);
        if (bitmap != null) {
            return bitmap;
        }
        return null;
    }

    /**
     * 存的操作
     */
    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        mLruCache.put(url, bitmap);
    }

}

磁盤緩存沒有寫,可以直接參考郭霖與張鴻洋博客,裏面分析了LruCache與DiskLruCache的使用與源碼

博客地址如下: http://blog.csdn.net/lmj623565791/article/details/47251585

http://blog.csdn.net/guolin_blog/article/details/28863651

http://blog.csdn.net/guolin_blog/article/details/34093441

VolleyTools只有一個實例,其實是mImageLoader,mLruMemImageCache,mQueue只需要初始化一次即可(單例)
public class VolleyTools {

ImageLoader         mImageLoader;
LruMemImageCache    mLruMemImageCache;
RequestQueue        mQueue;

public ImageLoader getImageLoader() {
    return mImageLoader;
}

public LruMemImageCache getLruMemImageCache() {
    return mLruMemImageCache;
}

public RequestQueue getQueue() {
    return mQueue;
}

private static VolleyTools  instance;

private VolleyTools(Context context) {
    mQueue = Volley.newRequestQueue(context);
    mLruMemImageCache = new LruMemImageCache();
    mImageLoader = new ImageLoader(mQueue, mLruMemImageCache);

}

public static VolleyTools getInstance(Context context) {
    if (instance == null) {
        synchronized (VolleyTools.class) {
            if (instance == null) {
                instance = new VolleyTools(context);
            }
        }
    }
    return instance;
}

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章