Volley是Google在Google I/O 2013上发布的一个网络框架,主要功能:web接口请求,网络图片异步下载,支持缓存。volley只是定义了缓存以及Request的接口,具体实现可以自己定义,例如lru磁盘缓存,内存缓存,下载图片的ImageRequest.
Volley的源代码里包含了一些实现,都在com.android.volley.toolbox包里,包括磁盘缓存、json请求,图片请求。还定义了一个继承自ImageView的NetworkImageView,可以异步载入网络图片。
项目地址:
https://android.googlesource.com/platform/frameworks/volley/
可能需要翻墙。
下面写个小例子,是请求百度图片api的,给各位参考下.
图方便,我把volley的源代码拷到自己项目里了.
百度图片接口地址为:http://image.baidu.com/channel/listjson?pn=42&rn=42&tag1=%E6%98%8E%E6%98%9F&tag2=%E6%98%9F%E9%97%BB%E6%98%9F%E4%BA%8B&ftags=&sorttype=0&ie=utf8&oe=utf-8&fr=channel&app=img.browse.channel.star
各位可以先看一下结构,针对接口的返回,定义一下Model,ImageListResponse.java:
- import java.util.ArrayList;
- public class ImageListResponse {
- ArrayList<BaiduImage> data;
- int totalNum;
- public ArrayList<BaiduImage> getData() {
- return data;
- }
- public void setData(ArrayList<BaiduImage> data) {
- this.data = data;
- }
- public class BaiduImage{
- String id,abs,desc,tag,date,image_url;
- public String getId() {
- return id;
- }
- public void setId(String id) {
- this.id = id;
- }
- public String getAbs() {
- return abs;
- }
- public void setAbs(String abs) {
- this.abs = abs;
- }
- public String getDesc() {
- return desc;
- }
- public void setDesc(String desc) {
- this.desc = desc;
- }
- public String getTag() {
- return tag;
- }
- public void setTag(String tag) {
- this.tag = tag;
- }
- public String getDate() {
- return date;
- }
- public void setDate(String date) {
- this.date = date;
- }
- public String getImage_url() {
- return image_url;
- }
- public void setImage_url(String image_url) {
- this.image_url = image_url;
- }
- }
- }
定义一个Request,继承自JsonRequest(就是为了用它实现的Listener,因为Request接口是没有实现deliverResponse方法的),ListRequest.java:
- import com.android.volley.NetworkResponse;
- import com.android.volley.ParseError;
- import com.android.volley.Response;
- import com.android.volley.toolbox.HttpHeaderParser;
- import com.android.volley.toolbox.JsonRequest;
- import com.google.gson.Gson;
- import com.google.gson.JsonSyntaxException;
- import java.io.UnsupportedEncodingException;
- public class ListRequest extends JsonRequest<ImageListResponse> {
- public ListRequest(Response.Listener<ImageListResponse> listener,Response.ErrorListener errorListener) {
- super(Method.GET, "http://image.baidu.com/channel/listjson?pn=42&rn=42&tag1=%E6%98%8E%E6%98%9F&tag2=%E6%98%9F%E9%97%BB%E6%98%9F%E4%BA%8B&ftags=&sorttype=0&ie=utf8&oe=utf-8&fr=channel&app=img.browse.channel.star",null,listener, errorListener);
- //用来取消请求的
- setTag(listener);
- }
- @Override
- protected Response<ImageListResponse> parseNetworkResponse(NetworkResponse response) {
- //配合Gson,转换成我们定义的ImageListResponse
- try {
- String json = new String(
- response.data, HttpHeaderParser.parseCharset(response.headers));
- Gson gson = new Gson();
- return Response.success(
- gson.fromJson(json, ImageListResponse.class), HttpHeaderParser.parseCacheHeaders(response));
- } catch (UnsupportedEncodingException e) {
- return Response.error(new ParseError(e));
- } catch (JsonSyntaxException e) {
- return Response.error(new ParseError(e));
- }
- }
- }
Activity里使用:
- import android.app.Activity;
- import android.os.Bundle;
- import com.android.volley.RequestQueue;
- import com.android.volley.Response;
- import com.android.volley.VolleyError;
- import com.android.volley.toolbox.Volley;
- import java.util.ArrayList;
- public class MainActivity extends Activity implements Response.Listener<ImageListResponse>,Response.ErrorListener{
- RequestQueue requestQueue;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //初始化
- requestQueue = Volley.newRequestQueue(this);
- //添加请求
- requestQueue.add(new ListRequest(this,this));
- }
- @Override
- public void onResponse(ImageListResponse response) {
- ArrayList<ImageListResponse.BaiduImage> images = response.data;
- for (ImageListResponse.BaiduImage image:images) {
- String imageUrl=image.getImage_url();
- if(imageUrl!=null)
- System.out.println(imageUrl);
- }
- }
- @Override
- public void onErrorResponse(VolleyError error) {
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- //取消请求,参数是tag
- requestQueue.cancelAll(this);
- requestQueue.stop();
- }
- }
Volley判断是否需要刷新缓存是使用服务端设置的,会考虑服务端返回header里的Cache-Control的Expires。但是有时候接口并不返回这些东西,这种情况下,volley设置的缓存ttl就是0,也就是相当于没有缓存,每次都会从网络请求,参考com.android.volley.toolbox.HttpHeaderParser.
这个时候,如果我们需要强制缓存,可以继承HttpHeaderParser,重载parseCacheHeaders方法.
- package com.android.volley.helper;
- import com.android.volley.Cache;
- import com.android.volley.NetworkResponse;
- import com.android.volley.toolbox.HttpHeaderParser;
- /**
- * 自定义的HeaderParser,跟默认的比,可以强制缓存,忽略服务器的设置
- */
- public class CustomHttpHeaderParser extends HttpHeaderParser {
- /**
- * Extracts a {@link com.android.volley.Cache.Entry} from a {@link com.android.volley.NetworkResponse}.
- *
- * @param response The network response to parse headers from
- * @param cacheTime 缓存时间,如果设置了这个值,不管服务器返回是否可以缓存,都会缓存,一天为1000*60*60*24
- * @return a cache entry for the given response, or null if the response is not cacheable.
- */
- public static Cache.Entry parseCacheHeaders(NetworkResponse response,long cacheTime) {
- Cache.Entry entry=parseCacheHeaders(response);
- long now = System.currentTimeMillis();
- long softExpire=now+cacheTime;
- entry.softTtl = softExpire;
- entry.ttl = entry.softTtl;
- return entry;
- }
- }
然后在Request的parseNetworkResponse方法里用CustomHttpHeaderParser.parseCacheHeaders(NetworkResponse response,long cacheTime)替代HttpHeaderParser.parseCacheHeaders(NetworkResponse response).
关于NetWorkImageView,调用setImageUrl(String url, ImageLoader imageLoader)方法设置图片,setDefaultImageResId(int defaultImage)设置在图片没下载完时显示的默认图片,setErrorImageResId(int errorImage)设置在图片下载失败时显示的图片.NetWorkImageView会自动根据自身的宽高读取图片,降低OOM的概率。
当NetworkImageView在ListView中使用时,ImageLoader会处理View复用的问题,不会重复给一个复用的ImageView设置图片.可以查看ImageContainer,ImageRequest了解相关实现.
ImageLoader需要一个ImageCache,用于处理图片缓存,配合开源项目DiskLruCache,我写了个ImageCache的实现,采用二级缓存,一级在内存中,一级在磁盘上.不过,磁盘读取文件还是在ui线程中,文件大了可能会有卡顿,以后再优化吧.
- package com.android.volley.helper;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.Environment;
- import android.util.LruCache;
- import com.android.volley.toolbox.ImageLoader;
- import com.jakewharton.disklrucache.DiskLruCache;
- import utils.MD5Utils;
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- /**
- * 二级Lru图片缓存,
- */
- public class LruImageCache implements ImageLoader.ImageCache {
- LruCache<String, Bitmap> lruCache;
- DiskLruCache diskLruCache;
- final int RAM_CACHE_SIZE = 5 * 1024 * 1024;
- String DISK_CACHE_DIR = "image";
- final long DISK_MAX_SIZE = 20 * 1024 * 1024;
- public LruImageCache() {
- this.lruCache = new LruCache<String, Bitmap>(RAM_CACHE_SIZE) {
- @Override
- protected int sizeOf(String key, Bitmap value) {
- return value.getByteCount();
- }
- };
- File cacheDir = new File(Environment.getExternalStorageDirectory(), DISK_CACHE_DIR);
- if(!cacheDir.exists())
- {
- cacheDir.mkdir();
- }
- try {
- diskLruCache = DiskLruCache.open(cacheDir, 1, 1, DISK_MAX_SIZE);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- @Override
- public Bitmap getBitmap(String url) {
- String key=generateKey(url);
- Bitmap bmp = lruCache.get(key);
- if (bmp == null) {
- bmp = getBitmapFromDiskLruCache(key);
- //从磁盘读出后,放入内存
- if(bmp!=null)
- {
- lruCache.put(key,bmp);
- }
- }
- return bmp;
- }
- @Override
- public void putBitmap(String url, Bitmap bitmap) {
- String key=generateKey(url);
- lruCache.put(url, bitmap);
- putBitmapToDiskLruCache(key,bitmap);
- }
- private void putBitmapToDiskLruCache(String key, Bitmap bitmap) {
- try {
- DiskLruCache.Editor editor = diskLruCache.edit(key);
- if(editor!=null)
- {
- OutputStream outputStream = editor.newOutputStream(0);
- bitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
- editor.commit();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- private Bitmap getBitmapFromDiskLruCache(String key) {
- try {
- DiskLruCache.Snapshot snapshot=diskLruCache.get(key);
- if(snapshot!=null)
- {
- InputStream inputStream = snapshot.getInputStream(0);
- if (inputStream != null) {
- Bitmap bmp = BitmapFactory.decodeStream(inputStream);
- inputStream.close();
- return bmp;
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
- /**
- * 因为DiskLruCache对key有限制,只能是[a-z0-9_-]{1,64},所以用md5生成key
- * @param url
- * @return
- */
- private String generateKey(String url)
- {
- return MD5Utils.getMD532(url);
- }
- }
MD5Utils是生成md5的类.