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).
如果如上面修改后 Cache.Entry entry=parseCacheHeaders(response);这里报空指针异常的话还需要修改,进parseCacheHeaders方法找到 headerValue = headers.get("Cache-Control");接下来会判断token.equals("no-cache") || token.equals("no-store"),如果为true则直接返回null,因此回报空指针异常,这里讲return null;注释掉保存则完成强制缓存
关于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的类.