Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Google教程——使用Volley加載網絡資源

Google教程——使用Volley加載網絡資源

編輯:關於Android編程

前言

因為最近想和後台進行對接,昨天自己在Google上研究了半天關於Volley的內容,覺得很開心。因為原來覺得關於網絡這塊,一直是個很復雜的東西和流程,沒想到Google已經推出了能把這方面封裝的這麼好的工具——Volley。通過它可以方便的實現小流量的數據傳輸,而且還可以根據自己的需求進行定制。其中我還自己看看json的東西,也在這個裡面小實踐了一下。

不過看完教程,我也慢慢看懂了一件事,現在我們學的這些框架都是Google,或者第三方的大神幫我們封裝好的,我們只需要按照接口去調用就行,而學完可只是學習了別人定義的方法怎麼使用,要進一步真正了解這一塊,應該深入其中的源碼,自己去理解其中的過程,甚至創造出一個自己的框架來。

因為Volley教程的最後一講是關於自定義Request的內容,通過查看一些StringRequest或是ImageRequest,我才發現,原來這些也都是大神們一步一步用JAVA語言編寫出來的。真正要繼續走下去還有很遠的路,要相信自己。


正文

這次我一邊看教程一邊對著敲代碼,自己做了一個測試的APP,沒有講究布局什麼的,先放一個圖出來:
這裡寫圖片描述

這裡寫圖片描述vcyzzLzHwrzRp7W9tcTE2sjdo7o8L3A+DQo8cD5Wb2xsZXnKx9K7uPZIVFRQtcRsaWKjrMv8yMNBbmRyb2lkIEFQULfDzsrN+MLnyv2+3bj8vNO88rWlv+y93aOsy/zT0LrctuDTxbXjo7o8L3A+DQrX1LavsLLFxc34wufH68fztfe2yCDNrMqxyrXP1rbguPbBrL3TIL/Jyei2qMfrx/O1xNPFz8i8tiDM4bmpyKHP+8frx/O1xEFQSSC3vbHjtqjWxiBhbmQgc28gb24NCjxwPlZvbGxledTaUlBDwODQzbLZ1/ejqNS2s8y197bI18rUtKOpwLS4/NDCVUm3vcPm0NTE3Ne/1L2ho8v8us2499bW0K3S6ba8xNy63LzytaW1xNX7us+jrNans9ZzdHJpbmcsIGltYWdlcywganNvbtXi0KnUrcn6yv2+3aGjVm9sbGV5ttTSu9Cpu/mxvrXEzfjC58frx/O2vL340NDBy7fi17CjrMTcsO/E473izdGz9rHg0LS7+bG+tPrC67XEv+C6o6OsyMPE47yv1tC+q8GmtKbA7bT6wuvC37ytoaM8L3A+DQo8cD5Wb2xsZXmyu8rKus+088G/tcTN+MLnwfey2df3o6zS8s6qy/zU2r3izva1xMqxuvKw0cv509C1xMfrx/O1xLfF1NrE2rTmwO+ho7bU09q088H3wb/PwtTYo6y/ydLUv7zCx9PDRG93bmxvYWRNYW5hZ2VyoaM8L3A+DQo8cD6908/CwLTIw87Sw8e/tL+01PXDtLyvs8lWb2xsZXm1vdfUvLrP7sS/wO/D5qO6PC9wPg0KPHA+tdrSu7K908NnaXQgY2xvbmUgdm9sbGV5o7o8YnIgLz4NCrTyv6rSu7j2sta/4qOsyLu689axvdPU2mdpdMnPw+bHw8nPyKXPwsPmtcS0+sLro7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> git clone https://android.googlesource.com/platform/frameworks/volley

這樣,我們就把volley項目克隆到了當前的倉庫文件夾下面了。

第二步加入自己的項目:
我用的是Andorid Studio,打開要引入Volley的項目,然後點擊File->New->Import Module,然後選中剛才的文件,這樣你就會發現除了app之外,又多了一個volley目錄。然後點擊File->Project Structure->選中APP,點擊Dependencies,選中右上角的綠色加號,選擇Module Dependency,然後選中volley就好了。

發送一個簡單的Request

基本模式是這樣的,首先創建一個RequestQueue,然後傳遞給它一個Request對象。RequestQueue負責管理網絡操作的線程,讀取或寫入緩存,解析響應。Request負責解析原始的響應信息。先看如何用默認的RequestQueue來完成這些操作:

基本操作

首先要添加訪問網絡的權限:

android.permission.INTERNET

首先我們用Volley提供的Volley.newRequestQueue方法建立一個默認的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() {
    @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的更新,比如說顯示一張圖片。但是我們也要注意及時的取消不必要的Request,避免造成主線程的阻塞。

Volley工作的大體流程

添加一個Request很簡單,就像之前代碼中展示的那樣,你先構造一個Request,然後用add()把它添加到RequestQueue裡面就好了。它會自動在並行的線程裡得到響應,處理網絡響應,發送給UI線程。

一旦你調用add(),Volley就會運行一個cache加工線程和網絡線程池,添加到RequestQueue之後,Volley會從先看看這個request能不能由cache處理,如果可以,直接由cache縣城處理,然後把解析過後的響應遞交到UI線程,如果不能,那這個請求就會被放在網絡隊列中,第一個可獲得的網絡線程就把它從隊列中取出來,執行HTTP操作,然後在工作線程中解析響應,把響應寫入換成,然後再把響應遞交給UI線程。

下面這張圖很清晰的描繪的整個流程:
這裡寫圖片描述

取消一個Request

調用cancel()裡取消一個Request,一旦取消,這個Reuqest就再也不能被調用。這意味著你可以在onStop()方法裡面取消所有的預備Request,然後不需要再看是否有Request的實例了,onSaveInstanceState()這些方法你也不必去擔心了。

用好cancel()方法能很好的處理Request,最好的途徑就是給Request添加一個Tag。我們可以給具有相同途徑的Request指定一個相同的標簽,然後在不需要它們的時候一起取消掉,合理實現了Request的分組管理。比如用ViewPaper顯示好幾屏的圖片,滑到下一頁時我們應該及時取消掉上一頁的Request,這樣它不會占著資源影響我們這一屏幕資源的加載。

下面是一個示例,分兩步:

第一步,給Request添加TAG

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);

第二步,在onStop()方法裡面取消掉所有有這個TAG的Request:

@Override
protected void onStop () {
    super.onStop();
    if (mRequestQueue != null) {
        mRequestQueue.cancelAll(TAG);
    }
}

當然,這些都是根據需求來寫的,如果你想預先加載准備好,你就不要調用cancel()。

建立一個RequestQueue

這個需求對於我這種剛入門的人應該不大,只要用Volley.newRequstQueue()創建一個默認的就好。不過以後肯定會因為需要去定制自己的RequestQueue,畢竟default並不能很好滿足項目開發。

建立一個Network和Cache

RequestQueue需要兩個東西,一個network負責傳輸request,一個緩存負責處理緩存。在Volley的工具箱裡都有這兩種的標准實現。DiskBasedCache提供帶有內存索引的一個文件一個響應的緩存,BasicNetwork提供一個你偏好的HTTP傳輸方式的network傳輸。

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() {
    @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);

// ...

如果你只想要實時的發送請求,不想把一些線程留在那裡不管,你可以在需要的時候創建一個ResqustQueue,然後在得到響應之後調用cancel()取消掉這個RequestQueue。不過更常見的一種方式是創建一個RequestQueue的單例,然後在你的APP的生命周期裡保持它一直運行著。

創建一個RequestQueue單例

如果APP經常使用網絡,建立起一個單例是一個很好的選擇,因為重復的創建是不必要的,不僅更加無謂的消耗運行內存,更重要的是讓緩存的存在意義基本變為0了。這裡有兩種方式,一種是實現一個單例,裡面有RequstQueue,裡面有其他的一些Volley功能方法。另一種是實現一個Application的子類,然後在onCreate()方法裡創建一個RequestQueue,不過官方更推薦第一種方法,因為它更加模塊化。

實現單例和APP的生命周期相同,傳入一個Application的上下文context就好了,注意不是Activity的。

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
                    cache = new LruCache(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  void addToRequestQueue(Request req) {
        getRequestQueue().add(req);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }
}

下面是使用單例的代碼:

// 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);

創建一個標准的Request

有時候APP訪問網絡數據希望得到string,images,json,滿足這些要求的request,Volley已經為我們封裝好了:

StringRequest 給出一個URL,返回一個原始的字符創資源 ImageRequest 給出一個URL,從response裡返回一個image JsonObjectRequest或是JsonArrayRequest都是給出一個URL,返回一個jsonobject或者是jsonarray

這些都是基本的Request,如果他們能滿足你的需求,你就直接用,下一節還會講怎麼訂制自己的Request。

請求一張Image

最開始的圖片展示展示了三張圖片,這三張圖片分別用了下面要介紹的三種加載網絡圖片的方式:

ImageRequest 給出一個圖片的URL,在回調方法裡返回一個Bitmap,它還提供了指定圖片尺寸大小的功能。它主要的好處是利用了Volley的自動調度功能,把昂貴的圖片開銷操作放在了工作worker線程(這個worker我不知道怎麼翻譯)

ImageLoader,這個類不僅能加載圖片還能把圖片緩存起來。它適合於處理大量的ImageRequest。比如所ListView的每一個Item都包含一個圖片,ImageLoader提供的緩存可以有效的方式圖片一閃而過的情況。如果能在緩存中找到圖片那麼就可以避免阻塞或是延遲UI線程。ImgaeLoader可以整合很多響應,沒有它的話基本每一個響應都要放一張圖片到View上面,布局不得不傳遞每一個圖片,整合使得同時處理多個響應成為可能,提高了加載性能。

NetworkImageView建立在ImageLoader上面,如果你的ImageView的圖片是從網絡上加載,那麼它是ImageView的一個很好的代替。如果View脫離了視圖層次,NetworkImageView也可以管理取消預備請求。

一個使用ImageRequest的例子

給一個url,然後把加載出來的圖片顯示到View上:

ImageView mImageView;
String url = "http://i.imgur.com/7spzG.png";
mImageView = (ImageView) findViewById(R.id.myImage);
...

// Retrieves an image specified by the URL, displays it in the UI.
ImageRequest request = new ImageRequest(url,
    new Response.Listener() {
        @Override
        public void onResponse(Bitmap bitmap) {
            mImageView.setImageBitmap(bitmap);
        }
    }, 0, 0, null,
    new Response.ErrorListener() {
        public void onErrorResponse(VolleyError error) {
            mImageView.setImageResource(R.drawable.image_load_error);
        }
    });
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(request);

使用ImageLoader

下面是一個使用ImageLoader的例子,給出一個URL,和一個ImageLoader.getImageListener即可:

ImageLoader mImageLoader;
ImageView mImageView;
// The URL for the image that is being loaded.
private static final String IMAGE_URL =
    "http://developer.android.com/images/training/system-ui.png";
...
mImageView = (ImageView) findViewById(R.id.regularImageView);

// Get the ImageLoader through your singleton class.
mImageLoader = MySingleton.getInstance(this).getImageLoader();
mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView,
         R.drawable.def_image, R.drawable.err_image));

使用單例的方式得到ImageLoader還是很重要,這關系到用戶體驗,如果用戶每次重新打開這個Activity,你有要重新去創建一個ImageLoader,那麼會照成圖片閃過,用戶等待的不好的情況,用單例的ImageLoader把圖片緩存起來是解決這個問題的有效方式。

使用NetworkImageView

給一個圖片URL,還有一個ImageLoader就行:

ImageLoader mImageLoader;
NetworkImageView mNetworkImageView;
private static final String IMAGE_URL =
    "http://developer.android.com/images/training/system-ui.png";
...

// Get the NetworkImageView that will display the image.
mNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView);

// Get the ImageLoader through your singleton class.
mImageLoader = MySingleton.getInstance(this).getImageLoader();

// Set the URL of the image that should be loaded into this view, and
// specify the ImageLoader that will be used to make the request.
mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);

LRU Cache的例子

我們加載圖片一定會用到緩存,Volley通過DiskBasedCache類提供了一個標准的緩存實現,它能直接把文件緩存到硬盤上的特定的文件夾中,如果使用ImageLoader,我麼應該提供一個定制好的LRU bitmap cache去實現ImageLoader.ImageCache接口。而且,這個最好是單例模式,因為圖片要緩存的原因。

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import com.android.volley.toolbox.ImageLoader.ImageCache;

public class LruBitmapCache extends LruCache
        implements ImageCache {

    public LruBitmapCache(int maxSize) {
        super(maxSize);
    }

    public LruBitmapCache(Context ctx) {
        this(getCacheSize(ctx));
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

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

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

    // Returns a cache size equal to approximately three screens worth of images.
    public static int getCacheSize(Context ctx) {
        final DisplayMetrics displayMetrics = ctx.getResources().
                getDisplayMetrics();
        final int screenWidth = displayMetrics.widthPixels;
        final int screenHeight = displayMetrics.heightPixels;
        // 4 bytes per pixel
        final int screenBytes = screenWidth * screenHeight * 4;

        return screenBytes * 3;
    }
}

下面是一個用cache實例化ImageLoader的例子:

RequestQueue mRequestQueue; // assume this exists.
ImageLoader mImageLoader = new ImageLoader(mRequestQueue, new LruBitmapCache(
            LruBitmapCache.getCacheSize()));

請求JSON

Volley提供了兩種標准的JSON請求:

JsonArrayRequest 給出一個URL,response返回一個JsonArray JsonObjectRequest 給出一個URL,response返回一個JsonObject
TextView mTxtDisplay;
ImageView mImageView;
mTxtDisplay = (TextView) findViewById(R.id.txtDisplay);
String url = "http://my-json-feed";

JsonObjectRequest jsObjRequest = new JsonObjectRequest
        (Request.Method.GET, url, null, new Response.Listener() {

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

    @Override
    public void onErrorResponse(VolleyError error) {
        // TODO Auto-generated method stub

    }
});

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

實現一個自定義的Request

如果你的response是一個string, image,或是json,你無需自定義,而如果你向的到一些其他的response,你就要自己去定義Request了。比如說你想修改一下JsonObjectRequest,你想直接通過json得到一個對象,那麼你就要自定義Request,官方教程中的GsonRequest就是講這個。

自定義Request有兩步:

繼承Request < T >類,< T >代表了你想解析得到的響應的類型,比如說你想在response中得到string類型的接口,那麼你就應該繼承Request< String > 實現parseNetworkResponse()和deliverResponse()抽象方法

parseNetworkResponse

一個Response裡面包括了你想要遞交的解析過後的Response:

@Override
protected Response 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參數,裡面包含了the response payload as a byte[], HTTP status code, and response headers.。。。。。不知道這些是什麼。 返回一個Response< T >,T代表的你想返回的response類型,比如StringRequest裡面這個位置是Request< String >。

deliverResponse

通過這個方法,Volley會把你在parseNetworkResponse()裡面解析得到的response直接返回給UI線程。

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

GsonRequest示例

下面是GsonRequest的示例,Gson是一個lib,能實現json和java對象的相互轉化:

public class GsonRequest extends Request {
    private final Gson gson = new Gson();
    private final Class clazz;
    private final Map headers;
    private final Listener 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 clazz, Map headers,
            Listener listener, ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        this.clazz = clazz;
        this.headers = headers;
        this.listener = listener;
    }

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

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

    @Override
    protected Response 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));
        }
    }
}

ps: Json小知識:

        Person one = new Person();
        one.setAge(1);
        one.setName("一號人物");
        Person two = new Person();
        two.setName("二號人物");
        two.setAge(2);
        Person[] personArray = new Person[]{one, two};
        String jsonString = JGsonSingleton.getInstance(this).getGson().toJson(personArray);
        jsonText.setText("將Person對象轉化為json字符串:" + "\n" + jsonString + "\n");

        Person[] jsonPerson = JGsonSingleton.getInstance(this).getGson().fromJson(jsonString, Person[].class);
        String personString = "";
        for (Person person : jsonPerson) {
            personString += person.toString();
        }
        jsonObject.setText("將json字符串轉化為Person對象: " + "\n" + personString + "\n");

自己寫的代碼,用Gson的toJson()方法直接把Person對象轉化為Json,也可以用Gson的fromJson()方法,把json字符串直接轉換為Person對象,單個對象的轉化是可以的,對象數組的轉化也是可以的。


好了,今天的內容就這麼多了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved