解析:
1)图片加载使用单例模式,避免多次调用时产生死锁
2)核心对象 LruCache
图片加载时先判断缓存里是否有图片,如果有,就使用缓存里的
没有就加载网络的,然后置入缓存
3)使用了线程池ExecutorService mThreadPool技术
4)使用了Semaphore 信号来控制变量按照先后顺序执行,避免空指针的问题
如何使用:
在Adapter里加载图片时
ImageLoader.getInstance.loadImage("http://www.baidu.com/images/kk.jpg", mImageView, true);
源码:
/** * @描述 图片加载类 * @项目名称 App_News * @包名 com.android.news.tools * @类名 ImageLoader * @author chenlin * @date 2015-3-7 下午7:35:28 * @version 1.0 */
public class ImageLoader {
private static ImageLoader mInstance;
/** * 图片缓存的核心对象 */
private LruCache<String, Bitmap> mLruCache;
/** * 线程池 */
private ExecutorService mThreadPool;
private static final int DEAFULT_THREAD_COUNT = 1;
/** * 队列的调度方式 */
private Type mType = Type.LIFO;
/** * 任务队列 */
private LinkedList<Runnable> mTaskQueue;
/** * 后台轮询线程 */
private Thread mPoolThread;
private Handler mPoolThreadHandler;
/** * UI线程中的Handler */
private Handler mUIHandler;
private Semaphore mSemaphorePoolThreadHandler = new Semaphore(0);
private Semaphore mSemaphoreThreadPool;
private boolean isDiskCacheEnable = true;
private static final String TAG = "ImageLoader";
public enum Type {
FIFO, LIFO;
}
private ImageLoader(int threadCount, Type type) {
init(threadCount, type);
}
/** * 初始化 * * @param threadCount * @param type */
private void init(int threadCount, Type type) {
initBackThread();
// 获取我们应用的最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheMemory = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap>(cacheMemory) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
// 创建线程池
mThreadPool = Executors.newFixedThreadPool(threadCount);
mTaskQueue = new LinkedList<Runnable>();
mType = type;
mSemaphoreThreadPool = new Semaphore(threadCount);
}
/** * 初始化后台轮询线程 */
private void initBackThread() {
// 后台轮询线程
mPoolThread = new Thread() {
@Override
public void run() {
Looper.prepare();
mPoolThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 线程池去取出一个任务进行执行
mThreadPool.execute(getTask());
try {
mSemaphoreThreadPool.acquire();
} catch (InterruptedException e) {
}
}
};
// 释放一个信号量
mSemaphorePoolThreadHandler.release();
Looper.loop();
};
};
mPoolThread.start();
}
public static ImageLoader getInstance() {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(DEAFULT_THREAD_COUNT, Type.LIFO);
}
}
}
return mInstance;
}
public static ImageLoader getInstance(int threadCount, Type type) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(threadCount, type);
}
}
}
return mInstance;
}
/** * 根据path为imageview设置图片 * * @param path * @param imageView */
public void loadImage(final String path, final ImageView imageView, final boolean isFromNet) {
imageView.setTag(path);
if (mUIHandler == null) {
mUIHandler = new Handler() {
public void handleMessage(Message msg) {
// 获取得到图片,为imageview回调设置图片
ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
Bitmap bm = holder.bitmap;
ImageView imageview = holder.imageView;
String path = holder.path;
// 将path与getTag存储路径进行比较
if (imageview.getTag().toString().equals(path)) {
imageview.setImageBitmap(bm);
}
};
};
}
// 根据path在缓存中获取bitmap
Bitmap bm = getBitmapFromLruCache(path);
if (bm != null) {
refreashBitmap(path, imageView, bm);
} else {
addTask(buildTask(path, imageView, isFromNet));
}
}
/** * 根据传入的参数,新建一个任务 * * @param path * @param imageView * @param isFromNet * @return */
private Runnable buildTask(final String path, final ImageView imageView, final boolean isFromNet) {
return new Runnable() {
@Override
public void run() {
Bitmap bm = null;
if (isFromNet) {
File file = getDiskCacheDir(imageView.getContext(), md5(path));
if (file.exists())// 如果在缓存文件中发现
{
Log.e(TAG, "find image :" + path + " in disk cache .");
bm = loadImageFromLocal(file.getAbsolutePath(), imageView);
} else {
if (isDiskCacheEnable)// 检测是否开启硬盘缓存
{
boolean downloadState = DownloadImgUtils.downloadImgByUrl(path, file);
if (downloadState)// 如果下载成功
{
Log.e(TAG,
"download image :" + path + " to disk cache . path is "
+ file.getAbsolutePath());
bm = loadImageFromLocal(file.getAbsolutePath(), imageView);
}
} else
// 直接从网络加载
{
Log.e(TAG, "load image :" + path + " to memory.");
bm = DownloadImgUtils.downloadImgByUrl(path, imageView);
}
}
} else {
bm = loadImageFromLocal(path, imageView);
}
// 3、把图片加入到缓存
addBitmapToLruCache(path, bm);
refreashBitmap(path, imageView, bm);
mSemaphoreThreadPool.release();
}
};
}
private Bitmap loadImageFromLocal(final String path, final ImageView imageView) {
Bitmap bm;
// 加载图片
// 图片的压缩
// 1、获得图片需要显示的大小
ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView);
// 2、压缩图片
bm = decodeSampledBitmapFromPath(path, imageSize.width, imageSize.height);
return bm;
}
/** * 从任务队列取出一个方法 * * @return */
private Runnable getTask() {
if (mType == Type.FIFO) {
return mTaskQueue.removeFirst();
} else if (mType == Type.LIFO) {
return mTaskQueue.removeLast();
}
return null;
}
/** * 利用签名辅助类,将字符串字节数组 * * @param str * @return */
public String md5(String str) {
byte[] digest = null;
try {
MessageDigest md = MessageDigest.getInstance("md5");
digest = md.digest(str.getBytes());
return bytes2hex02(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
/** * 方式二 * * @param bytes * @return */
public String bytes2hex02(byte[] bytes) {
StringBuilder sb = new StringBuilder();
String tmp = null;
for (byte b : bytes) {
// 将每个字节与0xFF进行与运算,然后转化为10进制,然后借助于Integer再转化为16进制
tmp = Integer.toHexString(0xFF & b);
if (tmp.length() == 1)// 每个字节8为,转为16进制标志,2个16进制位
{
tmp = "0" + tmp;
}
sb.append(tmp);
}
return sb.toString();
}
private void refreashBitmap(final String path, final ImageView imageView, Bitmap bm) {
Message message = Message.obtain();
ImgBeanHolder holder = new ImgBeanHolder();
holder.bitmap = bm;
holder.path = path;
holder.imageView = imageView;
message.obj = holder;
mUIHandler.sendMessage(message);
}
/** * 将图片加入LruCache * * @param path * @param bm */
protected void addBitmapToLruCache(String path, Bitmap bm) {
if (getBitmapFromLruCache(path) == null) {
if (bm != null)
mLruCache.put(path, bm);
}
}
/** * 根据图片需要显示的宽和高对图片进行压缩 * * @param path * @param width * @param height * @return */
protected Bitmap decodeSampledBitmapFromPath(String path, int width, int height) {
// 获得图片的宽和高,并不把图片加载到内存中
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = ImageSizeUtil.caculateInSampleSize(options, width, height);
// 使用获得到的InSampleSize再次解析图片
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
private synchronized void addTask(Runnable runnable) {
mTaskQueue.add(runnable);
// if(mPoolThreadHandler==null)wait();
try {
if (mPoolThreadHandler == null)
mSemaphorePoolThreadHandler.acquire();
} catch (InterruptedException e) {
}
mPoolThreadHandler.sendEmptyMessage(0x110);
}
/** * 获得缓存图片的地址 * * @param context * @param uniqueName * @return */
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
/** * 根据path在缓存中获取bitmap * * @param key * @return */
private Bitmap getBitmapFromLruCache(String key) {
return mLruCache.get(key);
}
private class ImgBeanHolder {
Bitmap bitmap;
ImageView imageView;
String path;
}
}
相关工具类:
/** * @描述 获取图片大小工具类s * @项目名称 App_News * @包名 com.android.news.util * @类名 ImageSizeUtil * @author chenlin * @date 2014-3-7 下午7:37:50 * @version 1.0 */
public class ImageSizeUtil {
/** * 根据需求的宽和高以及图片实际的宽和高计算SampleSize * * @param options * @param width * @param height * @return */
public static int caculateInSampleSize(Options options, int reqWidth, int reqHeight) {
int width = options.outWidth;
int height = options.outHeight;
int inSampleSize = 1;
if (width > reqWidth || height > reqHeight) {
int widthRadio = Math.round(width * 1.0f / reqWidth);
int heightRadio = Math.round(height * 1.0f / reqHeight);
inSampleSize = Math.max(widthRadio, heightRadio);
}
return inSampleSize;
}
/** * 根据ImageView获适当的压缩的宽和高 * * @param imageView * @return */
public static ImageSize getImageViewSize(ImageView imageView) {
ImageSize imageSize = new ImageSize();
DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
LayoutParams lp = imageView.getLayoutParams();
int width = imageView.getWidth();// 获取imageview的实际宽度
if (lp != null) {
if (width <= 0) {
width = lp.width;// 获取imageview在layout中声明的宽度
}
}
if (width <= 0) {
// width = imageView.getMaxWidth();// 检查最大值
width = getImageViewFieldValue(imageView, "mMaxWidth");
}
if (width <= 0) {
width = displayMetrics.widthPixels;
}
int height = imageView.getHeight();// 获取imageview的实际高度
if (lp != null) {
if (height <= 0) {
height = lp.height;// 获取imageview在layout中声明的宽度
}
}
if (height <= 0) {
height = getImageViewFieldValue(imageView, "mMaxHeight");// 检查最大值
}
if (height <= 0) {
height = displayMetrics.heightPixels;
}
imageSize.width = width;
imageSize.height = height;
return imageSize;
}
public static class ImageSize {
public int width;
public int height;
}
/** * 通过反射获取imageview的某个属性值 * * @param object * @param fieldName * @return */
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = field.getInt(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (Exception e) {
}
return value;
}
}