Android volley全局请求队列和图片加载
2015-12-01 04:52
519 查看
Android Volley网络通信详解一里,我介绍了Volley的基本特性,并且简单讲解了StringRequest和JsonObjectRequest。本篇,我们将创建一个全局的Volley请求,使你的应用方便使用volley。并且,讲解下volley的图片加载。
1. 创建一个请求队列
2. 创建一个请求
3. 把请求添加到请求队列
从上述步骤来看,这个过程似乎也不是很麻烦。不过,如果你深入Volley的源码,你会发现,上述过程,如果针对要不断发出网络请求的应用,是不合理的。
可以看到,按照上述的步骤,我们每次发出请求,都要去创建一个请求队列,这明显是不靠谱的。从源码可以看到,按照上面的写法,的确是在不断创建请求队列。
queue在每次newRequestQueue时会创建一个。
既然这种方案不合理,那怎么处理呢?合理的方案是,用一个单例去维护这个请求队列。
说到单例,在Android里,很容易想到Application。它在一个应用里只有一个。而且是全局的,哪里都能很容易拿到,可行。
不过,官网给我们提供的方案是自己维护一个。
在Application或者自己写个,这两个方案都可取。
代码很简单,就是多了个ImageLoader。这个会在后面的图片加载里深入讲解。
创建了这个VolleySingleton,之前的StringRequest和JsonRequest就可以不用再创建一个请求队列了,直接通过
ok,闲话少说。先来讲讲最基本的图片请求 : ImageRequest。
很简单吧,看看就行了。
前面,有说到Volley的网络图片加载是很强大、高效的。我们开发一些应用,往往需要加载大量的图片,并且这些图片基本上都是放到listview或者gridview里。这里,就需要考虑到发出请求的取消和确认。因为,你在滑动中,是在不断刷新item的位置的,当你从某一页滑到另外一页时,原先的“过期”请求最好能取消,而当请求收到响应,还要再确认是否和当前的item的请求地址一致。
如果是用ImageRequest,那么,你就得像我之前写的那个照片墙应用一样,要自己维护上面这些情况。
有了Volley的ImageLoader,你就再也不用这么苦逼的去处理这些了。
查看源码,可以看到,ImageLoader没有继承Request,而是维护一个请求队列和图片缓存。
根据上述代码,初始化ImageLoader需要一个RequestQueue和一个ImageCache。前者我们已经能很轻松的获取到了。
来说说ImageCache这个接口。从这个接口可以看出,需要实现一个缓存,键值对是String和Bitmap。
我们可以考虑用本地缓存DiskLruCache,也可以考虑内存缓存(关于这块,可以参考我以前写的Android Bitmap大量使用不产生OOM之使用缓存机制)。
这里,我用内存缓存管理类–LruCache,在support 4里有。
可以看到,我创建了一个LruBitmapCache继承自LruCache,并实现了ImageCache接口。
这里,缓存大小设置为3个界面大小。ps : 一个像素点占据4字节。
缓存创建完之后,就是关联到ImageLoader上。
上述代码是在VolleySingleton里,在第一次初始化VolleySingleton时,就会创建一个ImageLoader。
ok,初始工作完毕,接着就是怎么和url,ImageView关联起来。
我贴了一个常用的加载方式,ScaleType它已经默认设置了。
可以看到,ImageLoader.get需要4个参数:
1. 图片url地址
2. 创建一个ImageListener
3. 图片最大宽度
4. 图片最大高度
我们先来看看第2个参数–ImageListener。
上述代码是在ImageLoader里,也就是说,ImageListener可以从ImageLoader对象获取,需要传入3个参数:
1. 绑定图片到哪个ImageView
2. 没加载到图片时显示的图片资源
3. 加载结束,数据获取不到或数据异常时显示的加载出错图片
好了,ImageListener就介绍到这。接着讲讲下面两个参数:maxWidth和maxHeight。这两个参数设置,是为了告诉ImageLoader,我需要的图片最大只要这么宽,这么高,别超出,接近就行。然后在收到网络数据响应时,就会根据这两个值,对图片数据进行压缩。这样一来,最大限度的避免了OOM的发生。关于图片加载不出现OOM的方法可以参考: Android Bitmap大量使用不产生OOM之“加载大图片资源优化”。
可以看下Volley的源码,它的处理方式和我上面提到的博客里的处理机制是一样的。ps : 最后是在ImageRequest的doParse。
需要注意的是,如果maxWidth和maxHeight都为0,那就不压缩了。上面代码可以看到。
ok, ImageLoader的初始化和使用都讲完了。接着就是上个实例:
可以看到,效率很高。
详细代码如下:
好了,接着讲解关于Volley图片加载的最后一个利器:NetworkImageView。
这个可以认为是ImageLoader的封装版,啥意思呢?就是把ImageLoader封装到ImageView里,如下;
看到了吧,有个ImageLoader。那为啥要封装呢?其实就是为了方便我们更简单直观的写请求代码。举个例子,使用NetworkImageView发出请求是这样的:
上面三句话,就是一个完整的请求,包含未加载和加载出错处理。怎么样,很直观简单吧。直接就是调用NetworkImageView.setImageUrl就发出请求了。这就是封装后的效果。
ok,效果和上面的ImageLoader一样,我就再贴下吧。
很快,很高效。
实例代码和ImageLoader的那个实例代码几乎差不多,唯一的区别就在于GridView的item从原来的ImageView替换成了NetworkImageView,还有一个是load()的不同。
关于NetworkImageView的load的代码上面已经贴出来了,就是那三句话 : setDefaultImageResId、setErrorImageResId、setImageUrl。
最后,还需要说明的是,NetworkImageView它也有图片压缩机制,它的图片压缩大小根据NetworkImageView可从其parent那里获得的大小决定。
好了,关于Volley的单例维护请求队列和ImageLoader;关于Volley各种图片加载请求方式;这两块都讲完了。(写得多了。。)
Good Day!
Volley Singleton
我们先来回顾一下,发出一个请求,volley需要做哪些:1. 创建一个请求队列
2. 创建一个请求
3. 把请求添加到请求队列
从上述步骤来看,这个过程似乎也不是很麻烦。不过,如果你深入Volley的源码,你会发现,上述过程,如果针对要不断发出网络请求的应用,是不合理的。
可以看到,按照上述的步骤,我们每次发出请求,都要去创建一个请求队列,这明显是不靠谱的。从源码可以看到,按照上面的写法,的确是在不断创建请求队列。
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) { File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); String userAgent = "volley/0"; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } Network network = new BasicNetwork(stack); RequestQueue queue; if (maxDiskCacheBytes <= -1) { // No maximum size specified queue = new RequestQueue(new DiskBasedCache(cacheDir), network); } else { // Disk cache size specified queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network); } queue.start(); return queue; }
queue在每次newRequestQueue时会创建一个。
既然这种方案不合理,那怎么处理呢?合理的方案是,用一个单例去维护这个请求队列。
说到单例,在Android里,很容易想到Application。它在一个应用里只有一个。而且是全局的,哪里都能很容易拿到,可行。
不过,官网给我们提供的方案是自己维护一个。
public class VolleySingleton { private static VolleySingleton mInstance; private RequestQueue mRequestQueue; private ImageLoader mImageLoader; private static Context mContext; private VolleySingleton(Context context) { mContext = context; mRequestQueue = getRequestQueue(); mImageLoader = new ImageLoader( mRequestQueue, new LruBitmapCache(context) ); } public static synchronized VolleySingleton getInstance(Context context) { if (mInstance == null) { mInstance = new VolleySingleton(context); } return mInstance; } public RequestQueue getRequestQueue() { if (mRequestQueue == null) { // 如果你的应用时常使用volley,那么传入的context最好是application的context mRequestQueue = Volley.newRequestQueue(mContext.getApplicationContext()); } return mRequestQueue; } public <T> void addToRequestQueue(Request req) { getRequestQueue().add(req); } public ImageLoader getImageLoader() { return mImageLoader; } }
在Application或者自己写个,这两个方案都可取。
代码很简单,就是多了个ImageLoader。这个会在后面的图片加载里深入讲解。
创建了这个VolleySingleton,之前的StringRequest和JsonRequest就可以不用再创建一个请求队列了,直接通过
VolleySingleton.getInstance(this).addToRequestQueue(xxxRequest);就行。
图片加载
Volley提供了很强大的网络图片加载框架。之前我有写一个图片异步加载的照片墙应用,里面用到了Handler和Thread来模拟线程池,开了10个线程。Volley除了多线程执行外,还维护了一个本地缓存和内存缓存。所以在性能上更加高效。ok,闲话少说。先来讲讲最基本的图片请求 : ImageRequest。
ImageRequest
ImageRequest和之前讲到的StringRequest用法一样,都是继承Request。所以,直接贴代码:@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_image_request_test); mImg = (ImageView) findViewById(R.id.img); mImg.setImageResource(R.drawable.default_pic); final int screenWidth = DensityUtil.getScreenWidth(this); ImageRequest imageRequest = new ImageRequest( "http://pic31.nipic.com/20130711/9908282_130129471156_2.jpg", new Response.Listener<Bitmap>(){ @Override public void onResponse(Bitmap response) { mImg.setImageBitmap(response); } }, screenWidth, screenWidth/2, ImageView.ScaleType.CENTER, Bitmap.Config.ARGB_8888, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { mImg.setImageResource(R.drawable.default_pic); } } ); VolleySingleton.getInstance(this).addToRequestQueue(imageRequest); }
很简单吧,看看就行了。
ImageLoader
接着,我们来看下ImageLoader。前面,有说到Volley的网络图片加载是很强大、高效的。我们开发一些应用,往往需要加载大量的图片,并且这些图片基本上都是放到listview或者gridview里。这里,就需要考虑到发出请求的取消和确认。因为,你在滑动中,是在不断刷新item的位置的,当你从某一页滑到另外一页时,原先的“过期”请求最好能取消,而当请求收到响应,还要再确认是否和当前的item的请求地址一致。
如果是用ImageRequest,那么,你就得像我之前写的那个照片墙应用一样,要自己维护上面这些情况。
有了Volley的ImageLoader,你就再也不用这么苦逼的去处理这些了。
查看源码,可以看到,ImageLoader没有继承Request,而是维护一个请求队列和图片缓存。
public class ImageLoader { /** RequestQueue for dispatching ImageRequests onto. */ private final RequestQueue mRequestQueue; /** Amount of time to wait after first response arrives before delivering all responses. */ private int mBatchResponseDelayMs = 100; /** The cache implementation to be used as an L1 cache before calling into volley. */ private final ImageCache mCache; ...... public ImageLoader(RequestQueue queue, ImageCache imageCache) { mRequestQueue = queue; mCache = imageCache; } ...... public interface ImageCache { public Bitmap getBitmap(String url); public void putBitmap(String url, Bitmap bitmap); }
根据上述代码,初始化ImageLoader需要一个RequestQueue和一个ImageCache。前者我们已经能很轻松的获取到了。
来说说ImageCache这个接口。从这个接口可以看出,需要实现一个缓存,键值对是String和Bitmap。
我们可以考虑用本地缓存DiskLruCache,也可以考虑内存缓存(关于这块,可以参考我以前写的Android Bitmap大量使用不产生OOM之使用缓存机制)。
这里,我用内存缓存管理类–LruCache,在support 4里有。
public class LruBitmapCache extends LruCache<String, Bitmap> implements ImageLoader.ImageCache{ public LruBitmapCache(int maxSize) { super(maxSize); } public LruBitmapCache(Context context) { this(getCacheSize(context)); } @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } private static int getCacheSize(Context context) { // 这里可以根据你应用的实际需要,定义合适大小 // 这里设定缓存大小为3个整个界面所需图像数据大小 final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); final int screenWidth = displayMetrics.widthPixels; final int screenHeight = displayMetrics.heightPixels; final int screenBytes = screenWidth * screenHeight * 4; return screenBytes * 3; } @Override public Bitmap getBitmap(String url) { return get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { put(url, bitmap); } }
可以看到,我创建了一个LruBitmapCache继承自LruCache,并实现了ImageCache接口。
这里,缓存大小设置为3个界面大小。ps : 一个像素点占据4字节。
缓存创建完之后,就是关联到ImageLoader上。
mImageLoader = new ImageLoader( mRequestQueue, new LruBitmapCache(context) );
上述代码是在VolleySingleton里,在第一次初始化VolleySingleton时,就会创建一个ImageLoader。
ok,初始工作完毕,接着就是怎么和url,ImageView关联起来。
public ImageContainer get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight) { return get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE); }
我贴了一个常用的加载方式,ScaleType它已经默认设置了。
可以看到,ImageLoader.get需要4个参数:
1. 图片url地址
2. 创建一个ImageListener
3. 图片最大宽度
4. 图片最大高度
我们先来看看第2个参数–ImageListener。
public static ImageListener getImageListener(final ImageView view, final int defaultImageResId, final int errorImageResId)
上述代码是在ImageLoader里,也就是说,ImageListener可以从ImageLoader对象获取,需要传入3个参数:
1. 绑定图片到哪个ImageView
2. 没加载到图片时显示的图片资源
3. 加载结束,数据获取不到或数据异常时显示的加载出错图片
好了,ImageListener就介绍到这。接着讲讲下面两个参数:maxWidth和maxHeight。这两个参数设置,是为了告诉ImageLoader,我需要的图片最大只要这么宽,这么高,别超出,接近就行。然后在收到网络数据响应时,就会根据这两个值,对图片数据进行压缩。这样一来,最大限度的避免了OOM的发生。关于图片加载不出现OOM的方法可以参考: Android Bitmap大量使用不产生OOM之“加载大图片资源优化”。
可以看下Volley的源码,它的处理方式和我上面提到的博客里的处理机制是一样的。ps : 最后是在ImageRequest的doParse。
private Response<Bitmap> doParse(NetworkResponse response) { byte[] data = response.data; BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; if (mMaxWidth == 0 && mMaxHeight == 0) { decodeOptions.inPreferredConfig = mDecodeConfig; bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); } else { // If we have to resize this image, first get the natural bounds. decodeOptions.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); int actualWidth = decodeOptions.outWidth; int actualHeight = decodeOptions.outHeight; // Then compute the dimensions we would ideally like to decode to. int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, actualWidth, actualHeight, mScaleType); int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight, actualWidth, mScaleType); // Decode to the nearest power of two scaling factor. decodeOptions.inJustDecodeBounds = false; // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it? // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED; decodeOptions.inSampleSize = findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight); Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); // If necessary, scale down to the maximal acceptable size. if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) { bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true); tempBitmap.recycle(); } else { bitmap = tempBitmap; } } if (bitmap == null) { return Response.error(new ParseError(response)); } else { return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response)); } }
需要注意的是,如果maxWidth和maxHeight都为0,那就不压缩了。上面代码可以看到。
ok, ImageLoader的初始化和使用都讲完了。接着就是上个实例:
可以看到,效率很高。
详细代码如下:
public class ImageLoadTest extends ActionBarActivity { GridView mGridView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_image_load_test); mGridView = (GridView) findViewById(R.id.gridview); GridAdapter mAdapter = new GridAdapter(this); mGridView.setAdapter(mAdapter); } private static class GridAdapter extends BaseAdapter { Context mContext; final int mSize; @Override public int getCount() { return ImageUrl.IMAGE_URL.length; } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } public GridAdapter(Context context) { mContext = context; // 屏幕的3分之一 mSize = (DensityUtil.getScreenWidth(context) - DensityUtil.dpToPx(1, context.getResources())) / 3; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { viewHolder = new ViewHolder(); convertView = View.inflate(mContext, R.layout.gridview_image_item, null); viewHolder.imageView = (ImageView) convertView.findViewById(R.id.photo); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } AbsListView.LayoutParams params = (AbsListView.LayoutParams) viewHolder.imageView.getLayoutParams(); if (params == null) { params = new AbsListView.LayoutParams(mSize, mSize); viewHolder.imageView.setLayoutParams(params); } load(viewHolder.imageView, position); return convertView; } private void load(ImageView imageView, int position) { ImageLoader.ImageListener imageListener = ImageLoader.getImageListener(imageView, R.drawable.default_pic, R.drawable.default_pic); VolleySingleton.getInstance(mContext) .getImageLoader() .get(ImageUrl.IMAGE_URL[position], imageListener, mSize, mSize); } } private static class ViewHolder { ImageView imageView; }
好了,接着讲解关于Volley图片加载的最后一个利器:NetworkImageView。
这个可以认为是ImageLoader的封装版,啥意思呢?就是把ImageLoader封装到ImageView里,如下;
public class NetworkImageView extends ImageView { /** The URL of the network image to load */ private String mUrl; /** * Resource ID of the image to be used as a placeholder until the network image is loaded. */ private int mDefaultImageId; /** * Resource ID of the image to be used if the network response fails. */ private int mErrorImageId; /** Local copy of the ImageLoader. */ private ImageLoader mImageLoader; ......
看到了吧,有个ImageLoader。那为啥要封装呢?其实就是为了方便我们更简单直观的写请求代码。举个例子,使用NetworkImageView发出请求是这样的:
imageView.setDefaultImageResId(R.drawable.default_pic); imageView.setErrorImageResId(R.drawable.default_pic); imageView.setImageUrl(ImageUrl.IMAGE_URL[position], VolleySingleton.getInstance(mContext).getImageLoader());
上面三句话,就是一个完整的请求,包含未加载和加载出错处理。怎么样,很直观简单吧。直接就是调用NetworkImageView.setImageUrl就发出请求了。这就是封装后的效果。
ok,效果和上面的ImageLoader一样,我就再贴下吧。
很快,很高效。
实例代码和ImageLoader的那个实例代码几乎差不多,唯一的区别就在于GridView的item从原来的ImageView替换成了NetworkImageView,还有一个是load()的不同。
关于NetworkImageView的load的代码上面已经贴出来了,就是那三句话 : setDefaultImageResId、setErrorImageResId、setImageUrl。
最后,还需要说明的是,NetworkImageView它也有图片压缩机制,它的图片压缩大小根据NetworkImageView可从其parent那里获得的大小决定。
好了,关于Volley的单例维护请求队列和ImageLoader;关于Volley各种图片加载请求方式;这两块都讲完了。(写得多了。。)
Good Day!
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories