Volley 的图片加载的三种方式:
1、ImageRequest加入请求队列RequestQueue返回bitmap
ImageRequest中doParse()将图片解析后交由Response回调
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));
}
}
可以知道volley最终接受的不是stream而是byte[]所以加载大图片容易导致内存溢出
2 ImageLoader的方式
ImageLoader imageLoader = new ImageLoader(requestQueue, new ImageLoader.ImageCache() {
@Override
public void putBitmap(String url, Bitmap bitmap) {
}
@Override
public Bitmap getBitmap(String url) {
return null;
}
});
ImageLoader.ImageListener listener = ImageLoader.getImageListener(iv_second_image,
R.mipmap.ic_launcher, R.mipmap.ic_launcher);
imageLoader.get(imageUrl, listener);
ImageLoader支持缓存的方式:
get方法当中会先去构造时传入进去的缓存实例当中当中查找对应的缓存,查找到后交由listener中的onSponse方法将bitmap赋值给imageview控件
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
public static ImageListener getImageListener(final ImageView view,
final int defaultImageResId, final int errorImageResId) {
return new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (errorImageResId != 0) {
view.setImageResource(errorImageResId);
}
}
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
view.setImageBitmap(response.getBitmap());
} else if (defaultImageResId != 0) {
view.setImageResource(defaultImageResId);
}
}
};
当缓存不存在的时候依然是在get()方法中创建ImageRequest实例进行图片请求
Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
cacheKey);
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth, int maxHeight,
ScaleType scaleType, final String cacheKey) {
return new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight, scaleType, Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
}
ImageRequest后调用onGetImageSuccess先将图片加入缓存
protected void onGetImageSuccess(String cacheKey, Bitmap response) {
// cache the image that was fetched.
mCache.putBitmap(cacheKey, response);
// remove the request from the list of in-flight requests.
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
// Update the response bitmap.
request.mResponseBitmap = response;
// Send the batched response
batchResponse(cacheKey, request);
}
}
然后通过hanble.post返回主线程将图片资源赋给imageview控件
private void batchResponse(String cacheKey, BatchedImageRequest request) {
mBatchedResponses.put(cacheKey, request);
// If we don't already have a batch delivery runnable in flight, make a new one.
// Note that this will be used to deliver responses to all callers in mBatchedResponses.
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
for (BatchedImageRequest bir : mBatchedResponses.values()) {
for (ImageContainer container : bir.mContainers) {
// If one of the callers in the batched request canceled the request
// after the response was received but before it was delivered,
// skip them.
if (container.mListener == null) {
continue;
}
if (bir.getError() == null) {
container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);
} else {
container.mListener.onErrorResponse(bir.getError());
}
}
}
mBatchedResponses.clear();
mRunnable = null;
}
};
// Post the runnable.
mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
}
}
3、 NetworkImageView
ni_image_load.setDefaultImageResId(R.mipmap.ic_launcher);
ni_image_load.setErrorImageResId(R.mipmap.ic_launcher);
ni_image_load.setImageUrl(imageUrl,
imageLoader);
其中setImageUrl(imageUrl, imageLoader)方法当中调用了loadImageIfNessary方法,在这种有代码会发现依然是调用的的mImageLoader(url ,new ImageListener())继续下去还是调用的ImageRequest作为request请求
void loadImageIfNecessary(final boolean isInLayoutPass) {
int width = getWidth();
int height = getHeight();
ScaleType scaleType = getScaleType();
boolean wrapWidth = false, wrapHeight = false;
if (getLayoutParams() != null) {
wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT;
wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT;
}
// if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content
// view, hold off on loading the image.
boolean isFullyWrapContent = wrapWidth && wrapHeight;
if (width == 0 && height == 0 && !isFullyWrapContent) {
return;
}
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
// currently loaded image.
if (TextUtils.isEmpty(mUrl)) {
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
setDefaultImageOrNull();
return;
}
// if there was an old request in this view, check if it needs to be canceled.
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
if (mImageContainer.getRequestUrl().equals(mUrl)) {
// if the request is from the same URL, return.
return;
} else {
// if there is a pre-existing request, cancel it if it's fetching a different URL.
mImageContainer.cancelRequest();
setDefaultImageOrNull();
}
}
// Calculate the max image width / height to use while ignoring WRAP_CONTENT dimens.
int maxWidth = wrapWidth ? 0 : width;
int maxHeight = wrapHeight ? 0 : height;
// The pre-existing content of this view didn't match the current URL. Load the new image
// from the network.
// update the ImageContainer to be the new bitmap container.
mImageContainer = mImageLoader.get(mUrl,
new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (mErrorImageId != 0) {
setImageResource(mErrorImageId);
}
}
@Override
public void onResponse(final ImageContainer response, boolean isImmediate) {
// If this was an immediate response that was delivered inside of a layout
// pass do not set the image immediately as it will trigger a requestLayout
// inside of a layout. Instead, defer setting the image by posting back to
// the main thread.
if (isImmediate && isInLayoutPass) {
post(new Runnable() {
@Override
public void run() {
onResponse(response, false);
}
});
return;
}
if (response.getBitmap() != null) {
setImageBitmap(response.getBitmap());
} else if (mDefaultImageId != 0) {
setImageResource(mDefaultImageId);
}
}
}, maxWidth, maxHeight, scaleType);
}
可以在这之中添加Lru缓存
public class BitMapCache implements ImageCache {
private LruCache<String, Bitmap> mCache;
public BitMapCache() {
int maxSize = 10 * 1024 * 1024;
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
@Override
public Bitmap getBitmap(String url) {
Bitmap bitmap= mCache.get(url);
if(bitmap!=null){
Log.e("BitMapCache取空了","url="+url);
}else{
Log.e("BitMapCache取有值:","url="+url);
}
return bitmap;
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
Log.e("BitMapCache存","url="+url);
mCache.put(url, bitmap);
}
}
修改一下添加一下三级缓存,先去lruCache内存中取,不存在读取sd卡中的文件
public class BitmapLruCacher implements ImageLoader.ImageCache {
private LruCache<String, Bitmap> lruCache;
public BitmapLruCacher() {
lruCache = new LruCache<String, Bitmap>(
(int) Runtime.getRuntime().maxMemory() / 20) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
public Bitmap getBitmap(String url) {
if (StringUtils.isEmpty(url) || JSONObject.NULL.equals(url)) { //这里需要判断URL是否合法 暂时这样写着
return null;
}
return lruCache.get(url);
}
@Override
public Bitmap getBitMap(String url) { //通过SD卡缓存去寻找
Bitmap bitmap = lruCache.get(url);
if (bitmap == null) {
String fileName = url.substring(url.lastIndexOf("/") + 1);
/**
* 如果为null,则缓存中没有,从本地SD卡缓存中找
*/
File cacheDir = new File(FileHelper.getImgPath(1));
File[] cacheFiles = cacheDir.listFiles();
if (cacheFiles != null) {
int i = 0;
for (; i < cacheFiles.length; i++) {
if (TextUtils.equals(fileName, cacheFiles[i].getName()))
break;
}
/**
* 若找到则返回bitmap否则返回null
*/
if (i < cacheFiles.length) {
bitmap = getSDBitmap(FileHelper.getImgPath(1) + "/"
+ fileName);
/**
* 将从SD卡中获取的bitmap放入缓存中
*/
if(url!=null&&bitmap!=null){
lruCache.put(url, bitmap);
}
}
}
}
return bitmap;
}
@Override
public void putBitMap(String url, Bitmap bitmap) { //缓存SD卡一份
if (getBitmap(url) == null) {
lruCache.put(url, bitmap);
}
putSDBitmap(url.substring(url.lastIndexOf("/") + 1), bitmap);
}
private Bitmap getSDBitmap(String imgPath) {
BitmapFactory.Options options = new BitmapFactory.Options();
/**
* 设置临时缓存大小
*/
options.inTempStorage = new byte[1024 * 1024];
/**
* 通过设置Options.inPreferredConfig值来降低内存消耗: 默认为ARGB_8888: 每个像素4字节. 共32位。
* Alpha_8: 只保存透明度,共8位,1字节。 ARGB_4444: 共16位,2字节。 RGB_565:共16位,2字节
*/
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
/**
* inPurgeable:设置为True,则使用BitmapFactory创建的Bitmap用于存储Pixel的内存空间,
* 在系统内存不足时可以被回收,当应用需要再次访问该Bitmap的Pixel时,系统会再次调用BitmapFactory
* 的decode方法重新生成Bitmap的Pixel数组。 设置为False时,表示不能被回收。
*/
options.inPurgeable = true;
options.inInputShareable = true;
/**
* 设置decode时的缩放比例。
*/
options.inSampleSize = 1;
return BitmapFactory.decodeFile(imgPath, options);
}
private void putSDBitmap(final String fileName, final Bitmap bitmap) {
File cacheDir = new File(FileHelper.getImgPath(1));
if (!cacheDir.exists())
cacheDir.mkdirs();
File cacheFile = new File(FileHelper.getImgPath(1) + "/"
+ fileName);
if (!cacheFile.exists()) {
try {
cacheFile.createNewFile();
} catch (Exception e) {
e.printStackTrace();
}
}
FileOutputStream fos;
try {
fos = new FileOutputStream(cacheFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void putBitmap(String url, Bitmap bitmap) {
if (getBitmap(url) == null) {
lruCache.put(url, bitmap);
}
}
public void cleanBitMap() { //清除缓存
if (lruCache != null) {
lruCache.evictAll();
}
}
}
上面这个需要修改一下对应的重载方法 或者修改ImageLoader当中的get方法
一个比较好的缓存博客点击打开链接