您的位置:首页 > 移动开发 > Android开发

volley中ImageLoader的理解和扩张

2015-11-13 19:48 489 查看
没有了解过Vollery的朋友,请自行google了解.这里不做复述.

先来看ImageLoader的关键源码:

public ImageContainer get(String requestUrl, ImageListener imageListener,

int maxWidth, int maxHeight) {

// only fulfill requests that were initiated from the main thread.

throwIfNotOnMainThread();

final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

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

}

// The bitmap did not exist in the cache, fetch it!

ImageContainer imageContainer = new ImageContainer(null, requestUrl,

cacheKey, imageListener);

// Update the caller to let them know that they should use the default

// bitmap.

imageListener.onResponse(imageContainer, true);

// Check to see if a request is already in-flight.

BatchedImageRequest request = mInFlightRequests.get(cacheKey);

if (request != null) {

// If it is, add this request to the list of listeners.

request.addContainer(imageContainer);

return imageContainer;

}

// The request is not already in flight. Send the new request to the

// network and

// track it.

Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth,

maxHeight, cacheKey);

mRequestQueue.add(newRequest);

mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest,

imageContainer));

return imageContainer;

}

这个是请求图片的接口,可以看到,他通过图片路径+高度+宽度生成了一个缓存的Key.然后会通过这个Key去mCache中查找是否有缓存图片.如果有的话,会直接直接把图片包装成一个ImageContainer.且直接返回,如果没有找找到的话.会生成一个一个请求,将这条请求入队.执行策略的话,内部是一个优先级的阻塞队列.而ImageRequest的优先级是low.在ImageRequest这个类的getPriority()接口会返回优先级.注意的是,onResponse接口的第二参数,表示是否是及时返回的.如果为true时,一般表示图片在内存中,所以一般如果参数为True的话,不用启动动画效果.为false的话表示进行了网络请求.这时即可对imageview进行一些动画的过度.

protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth,

int maxHeight, final String cacheKey) {

return new ImageRequest(requestUrl, new Listener<Bitmap>() {

@Override

public void onResponse(Bitmap response) {

onGetImageSuccess(cacheKey, response);

}

}, maxWidth, maxHeight, Config.RGB_565, new ErrorListener() {

@Override

public void onErrorResponse(VolleyError error) {

onGetImageError(cacheKey, error);

}

});

}

这个接口生产了一个ImageRequest对象.成功时会触发

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

}

}

失败时会触发

protected void onGetImageError(String cacheKey, VolleyError error) {

// Notify the requesters that something failed via a null result.

// Remove this request from the list of in-flight requests.

BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

if (request != null) {

// Set the error for this request

request.setError(error);

// Send the batched response

batchResponse(cacheKey, request);

}

}

可以重写这2个接口,进行本地的保存操作.ImageLoader的源码简单的介绍到这里.接下来看如何在原有基础上增加一些拓展功能.

package com.chediandian.core.image;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import android.graphics.Bitmap;

import android.graphics.Bitmap.CompressFormat;

import com.android.volley.RequestQueue;

import com.android.volley.VolleyError;

import com.android.volley.toolbox.ImageLoader;

import com.chediandian.core.image.cache.LruBitmapCache;

import com.chediandian.core.image.cache.local.name.LocalName;

import com.chediandian.core.image.utils.XKImageUtils;

/**

* <p>

* <b>ImageLoader增强版</b>

* </p>

* <li>支持本地缓存</li> <li>支持取消指定的图片请求</li> <li>比之前更强大的内存缓存池</li>

*

* @version 1.0

* @since 1.0

* @author Shun

* @hide 暂时不对外不暴露

*

*/

class XKImageLoader extends ImageLoader {

private LruBitmapCache mCache;

private LocalName mLocalName;

private String mLocalDirsPath;

private ExecutorService mPool;

private Map<String, ImageContainer> mRequests;

/**

*

* @param queue

* @param imageCache

* @param localDirsPath

* 本地缓存文件夹路径

* @param localName

* 文件名策略接口

*/

public XKImageLoader(RequestQueue queue, ImageCache imageCache,

String localDirsPath, LocalName localName) {

super(queue, imageCache);

// 这里记录引用,为了添加本地缓存

mCache = (LruBitmapCache) imageCache;

mLocalDirsPath = localDirsPath;

mLocalName = localName;

mPool = Executors.newFixedThreadPool(2);

mRequests = new ConcurrentHashMap<String, ImageContainer>();

}

/**

* 复写get方法,增加本地是否有缓存的逻辑

*/

@Override

public ImageContainer get(String requestUrl, ImageListener imageListener,

int maxWidth, int maxHeight) {

String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

// 不在缓存,则从本地找

if (mCache.getBitmap(cacheKey) == null) {

File file = new File(mLocalDirsPath,

mLocalName.getFileName(cacheKey));

// 如果缓存文件存在,那么加入到缓存

if (file.exists()) {

mPool.execute(new LoadLocalTask(cacheKey, file

.getAbsolutePath(), requestUrl, imageListener,

maxWidth, maxHeight));

return null;

}

}

ImageContainer container = super.get(requestUrl, imageListener,

maxWidth, maxHeight);

// 如果bitmap为空,表示容器中没有缓存,即把它添加到请求Map中

if (null == container.getBitmap()) {

mRequests.put(cacheKey, container);

}

return container;

}

/**

* 加载本地图片

*

* @param path

* 图片路径

* @param imageListener

* 图片监听

* @param maxWidth

* 最大宽度

* @param maxHeight

* 最大高度

*/

public void loadImage(String path, ImageListener imageListener,

int maxWidth, int maxHeight) {

String key = getCacheKey(path, maxWidth, maxHeight);

// 不在缓存,则从本地找

if (mCache.getBitmap(key) == null) {

File file = new File(path);

// 如果缓存文件存在,那么加入到缓存

if (file.exists()) {

mPool.execute(new LoadLocalTask(key, file.getAbsolutePath(),

path, imageListener, maxWidth, maxHeight));

}

} else {

Bitmap bitmap = mCache.getBitmap(key);

ImageContainer container = new ImageContainer(bitmap, path, null,

null);

imageListener.onResponse(container, true);

}

}

/**

* 复写onGetImageSuccess,增加线程池来管理持久化

*/

@Override

protected void onGetImageSuccess(String cacheKey, Bitmap response) {

// 持久化图片缓存

File file = new File(mLocalDirsPath, mLocalName.getFileName(cacheKey));

if (!file.exists()) {

mPool.execute(new LocalTask(file.getAbsolutePath(), response));

}

super.onGetImageSuccess(cacheKey, response);

// 从请求Map中移除这条请求,因为它已经完成

mRequests.remove(cacheKey);

}

@Override

protected void onGetImageError(String cacheKey, VolleyError error) {

super.onGetImageError(cacheKey, error);

// 从请求Map中移除这条请求,因为它已经完成

mRequests.remove(cacheKey);

}

/**

* <p>

* <b>持久化图片到本地的Task</b>

* </P>

*

* @version 1.0

* @since 1.0

* @author Shun

*

*/

private class LocalTask implements Runnable {

private String mPath;

private Bitmap mBitmap;

public LocalTask(String path, Bitmap bitmap) {

this.mPath = path;

this.mBitmap = bitmap;

}

@Override

public void run() {

FileOutputStream out = null;

try {

if (null != mBitmap) {

out = new FileOutputStream(mPath);

mBitmap.compress(CompressFormat.JPEG, 100, out);

}

} catch (FileNotFoundException e) {

e.printStackTrace();

} finally {

if (out != null) {

try {

out.close();

out = null;

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

}

/**

* 加载本地图片Task

*

* @version 1.0

* @since 1.0

* @author Shun

*

*/

private class LoadLocalTask implements Runnable {

String mCacheKey;

String mPath;

String mUrl;

ImageListener mImageListener;

int mWidth;

int mHeight;

public LoadLocalTask(String cacheKey, String path, String url,

ImageListener imageListener, int width, int height) {

this.mCacheKey = cacheKey;

this.mPath = path;

this.mUrl = url;

this.mImageListener = imageListener;

this.mWidth = width;

this.mHeight = height;

}

@Override

public void run() {

Bitmap bitmap = XKImageUtils.loadBitmap(mPath, mWidth, mHeight);

if (null != bitmap) {

mCache.putBitmap(mCacheKey, bitmap);

}

// 这个Hanlder是父类的全局变量

mHandler.postDelayed(new DeliverTask(bitmap, mUrl, mImageListener),

100);

}

}

/**

* 转发到主线程的Task

*

* @version 1.0

* @since 1.0

* @author Shun

*

*/

private class DeliverTask implements Runnable {

Bitmap mCacheBitmap;

String mUrl;

ImageListener mImageListener;

public DeliverTask(Bitmap cacheBitmap, String url,

ImageListener imageListener) {

this.mCacheBitmap = cacheBitmap;

this.mUrl = url;

this.mImageListener = imageListener;

}

@Override

public void run() {

ImageContainer container = new ImageContainer(mCacheBitmap, mUrl,

null, null);

if (null != mCacheBitmap)

mImageListener.onResponse(container, true);

else

mImageListener.onErrorResponse(null);

}

}

/**

*

* <p>

* <b>取消所有图片请求</b>

* </p>

* 注意的是,此类接口对本地图片无效

*/

public void cancelRequest() {

for (String key : mRequests.keySet()) {

ImageContainer container = mRequests.get(key);

if (null != container)

container.cancelRequest();

}

mRequests.clear();

}

/**

*

* <p>

* <b>取消指定的请求</b>

* </p>

*

* @param url

* 请求的URL

*/

public void cancelRequest(String url) {

cancelRequest(url, 0, 0);

}

/**

*

* <p>

* <b>取消指定的请求</b>

* </p>

*

* @param url

* 请求的URL

* @param maxWidth

* 加载时设置的宽度

* @param maxHeight

* 加载时设置的高度

*/

public void cancelRequest(String url, int maxWidth, int maxHeight) {

String key = getCacheKey(url, maxWidth, maxHeight);

ImageContainer container = mRequests.remove(key);

if (null != container) {

container.cancelRequest();

}

}

/**

*

* <p>

* <b>清除所有缓存</b>

* </p>

*/

public void clearCache() {

if (null != mCache) {

mCache.clear();

}

}

/**

*

* <p>

* <b>清除指定缓存</b>

* </p>

*

* @param urlOrPath

* 可以是Url可以是本地路径

*/

public boolean cearCache(String urlOrPath) {

return clearCache(urlOrPath, 0, 0);

}

/**

*

* <p>

* <b>清除指定缓存</b>

* </p>

* 注意maxWidth,maxHeight必须与缓存请求时的大小保持一致

*

* @param urlOrPath

* 可以是Url可以是本地路径

* @param maxWidth

* 加载时设置的宽度

* @param maxHeight

* 加载时设置的高度

*/

public boolean clearCache(String urlOrPath, int maxWidth, int maxHeight) {

if (null != mCache) {

Bitmap bitmap = mCache.remove(getCacheKey(urlOrPath, maxWidth,

maxHeight));

if (null != bitmap) {

if (!bitmap.isRecycled()) {

bitmap.recycle();

}

bitmap = null;

return true;

} else {

return false;

}

}

return false;

}

}

可以看到 我重写了onGetImageSuccess,onGetImageError,get
3个接口来实现本地化功能.大家可以看一下,注释先的很清楚,DeliverTask主要用于异步加载本地图片后调度到主线程.LocalTask主要用于图片的持久化.LoadLocalTask加载本地图片.需要注意的是mHandler. mHandler在源码中是私有的.我自己改成了受保护的.

get接口中,会先检查是否有缓存,没有缓存的话从本地找,如果本地有的话会讲一个任务放到线程池中去执行. 最后由DeliverTask 调度到主线程中 关键代码:

String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

// 不在缓存,则从本地找

if (mCache.getBitmap(cacheKey) == null) {

File file = new File(mLocalDirsPath,

mLocalName.getFileName(cacheKey));

// 如果缓存文件存在,那么加入到缓存

if (file.exists()) {

mPool.execute(new LoadLocalTask(cacheKey, file

.getAbsolutePath(), requestUrl, imageListener,

maxWidth, maxHeight));

return null;

}

}

重写onGetImageSuccess的目的是为了在图片获取成功时将图片保存到本地
如果本地不存在,会执行一个持久化任务, 同样也是通过线程.

File file = new File(mLocalDirsPath, mLocalName.getFileName(cacheKey));

if (!file.exists()) {

mPool.execute(new LocalTask(file.getAbsolutePath(), response));

}

至此,一个支持本地功能的ImageLoader完成,看注释应该很容易看懂代码,这里就不细讲了 :)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  volley android