完整代码在最后,可直接拿去用。
先说一下思路:
ImageLoader类用来控制图片的加载,特别是遇到很多请求的时候,就要对任务进行排队。所以这里就产生两个points,一是imagelodaer要确保使用单例模式,二是要建立合理的线程池来确保众多任务的处理。
所以我们应用的时候,使用静态方法来返回该类的对象,如果没有则new一个返回,如果已经有了,则返回已存在的。
public static ImageLoader getInstance(int threadCount, Type type) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(threadCount, type);
}
}
}
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;
}
因为在一开始时,可能多个地方(比如 adapter里getView的时候)同时进行了第一个if判断,所以这里用了两个if来判断是否为空,确保单例。
对于第二个问题,我们在ImageLoader内部建立一个线程池,让其不断轮询,从线程池里取出任务来执行。
实现方法,在Thread里面用Looper,然后每次从线程池里取出一个任务来执行。
// 后台轮询线程
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) {
e.printStackTrace();
}
}
};
mSemaphorePoolThreadHandler.release();
Looper.loop();
}
};
mPoolThread.start();
// 后台轮询线程
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) {
e.printStackTrace();
}
}
};
mSemaphorePoolThreadHandler.release();
Looper.loop();
}
};
mPoolThread.start();
然后,任务就是从图片路径来获取图片,然后压缩并为imageview设置图片。
首先,我们需要一块缓存来存储压缩好的图片,节省资源,并防止内存溢出。我们可以用LruCache来实现,我们用图片路径为key,bitmap为value;
int maxMemory = (int) Runtime.getRuntime().maxMemory();// 获取最大可用内存
int cacheMemory = maxMemory / 8; // 设置为最大可用内存的1/8;
mLruCache = new LruCache<String, Bitmap>(cacheMemory) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
int maxMemory = (int) Runtime.getRuntime().maxMemory();// 获取最大可用内存
int cacheMemory = maxMemory / 8; // 设置为最大可用内存的1/8;
mLruCache = new LruCache<String, Bitmap>(cacheMemory) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
接下来计算ImageView的大小,在android api 16 以上的版本中,我们可以直接使用imageView.getMaxWidth() getMaxHeight(),但是为了保证兼容性问题,这里利用java的反射机制来实现:
protected ImageSize getImageViewSize(ImageView imageView) {
ImageSize imageSize = new ImageSize();
DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
LayoutParams lp = imageView.getLayoutParams();
int width = imageView.getWidth();
if (width <= 0) {
width = lp.width;
}
if (width <= 0) {
// android 16 以上可用
// width = imageView.getMaxWidth();
width = getImageViewFieldValue(imageView, "mMaxWidth");
}
if (width <= 0) {
width = displayMetrics.widthPixels;
}
int height = imageView.getHeight();
if (height <= 0) {
height = lp.height;
}
if (height <= 0) {
height = getImageViewFieldValue(imageView, "mMaxHeight");
}
if (height <= 0) {
height = displayMetrics.heightPixels;
}
imageSize.width = width;
imageSize.height = height;
return imageSize;
}
protected ImageSize getImageViewSize(ImageView imageView) {
ImageSize imageSize = new ImageSize();
DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
LayoutParams lp = imageView.getLayoutParams();
int width = imageView.getWidth();
if (width <= 0) {
width = lp.width;
}
if (width <= 0) {
// android 16 以上可用
// width = imageView.getMaxWidth();
width = getImageViewFieldValue(imageView, "mMaxWidth");
}
if (width <= 0) {
width = displayMetrics.widthPixels;
}
int height = imageView.getHeight();
if (height <= 0) {
height = lp.height;
}
if (height <= 0) {
height = getImageViewFieldValue(imageView, "mMaxHeight");
}
if (height <= 0) {
height = displayMetrics.heightPixels;
}
imageSize.width = width;
imageSize.height = height;
return imageSize;
}
我们设置好ImageView的实际大小后,接着来压缩图片,我们使用BitmapFactory来压缩,这里需要两个参数,照片路径和尺寸。
private Bitmap decodeSampledBitMapFromPath(String path, int width, int height) {
// 获取图片的宽和高,并不把图片加载到内存中
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = caculateSampleSize(options, width, height);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
private Bitmap decodeSampledBitMapFromPath(String path, int width, int height) {
// 获取图片的宽和高,并不把图片加载到内存中
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = caculateSampleSize(options, width, height);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
尺寸是指要压缩成的尺寸,这里我们可以根据自己的需要来计算比例
private int caculateSampleSize(BitmapFactory.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;
}
private int caculateSampleSize(BitmapFactory.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;
}
整理思路介绍完了,还有几个要点必须要说一下。
因为使用了线程池,所以我们必须要限制一下线程数,不然照样内存溢出,我们使用锁来确保最大线程数不会超过我们预定的线程数。
使用方法:
ImageLoader.getInstance(maxThreadCount, QueueType).loadImage(path, imageview);
ImageLoader.getInstance(maxThreadCount, QueueType).loadImage(path, imageview);
getInstance第一个参数是设置最大同时进行的线程数,第二个是队列出队方式,上文没有给出说明,可以看代码,很容易理解。loadImage两参数,一是路径,二是ImageView。
最后附完整代码:
package org.out.naruto.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.LruCache;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @author Hao_S
*/
public class ImageLoader {
private static final String TAG = "ImageLoader";
private static ImageLoader mInstance;
private LruCache<String, Bitmap> mLruCache;
private ExecutorService mThreadPool;
private static final int DEAFUT_THREAD_COUNT = 1;
/**
* 队列调度方式
*/
private Type mType = Type.LIFO;
private LinkedList<Runnable> mTaskQueue;
private Thread mPoolThread;
private Handler mPoolThreadHandler;
private Handler mUIHandler;
private Semaphore mSemaphorePoolThreadHandler = new Semaphore(0);
private Semaphore mSemaphoreTHreadPool;
public enum Type {
LIFO, FIFO
}
private ImageLoader(int mThreadCount, Type type) {
init(mThreadCount, type);
}
private void init(int threadCount, Type type) {
// 后台轮询线程
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) {
e.printStackTrace();
}
}
};
mSemaphorePoolThreadHandler.release();
Looper.loop();
}
};
mPoolThread.start();
int maxMemory = (int) Runtime.getRuntime().maxMemory();// 获取最大可用内存
int cacheMemory = maxMemory / 8; // 设置为最大可用内存的1/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 Runnable getTask() {
if (mType == Type.FIFO) {
return mTaskQueue.removeFirst();
} else {
return mTaskQueue.removeLast();
}
}
/**
* 单例模式
*/
public static ImageLoader getInstance(int threadCount, Type type) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(threadCount, type);
}
}
}
return mInstance;
}
public void loadImage(final String path, final ImageView imageView) {
imageView.setTag(path);
if (mUIHandler == null) {
mUIHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 获取得到的图片,为ImageView回调设置图片
ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
Bitmap bm = holder.bitmap;
ImageView imageView = holder.imageView;
String path = holder.path;
if (imageView.getTag().toString().equals(path)) {
imageView.setImageBitmap(bm);
}
}
};
}
Bitmap bm = getBitmapFromLruCache(path);
if (bm != null) {
refreashBitmap(bm, imageView, path);
} else {
addTask(new Runnable() {
@Override
public void run() {
// 加载图片 压缩
ImageSize imageSize = getImageViewSize(imageView);
Bitmap bm = decodeSampledBitMapFromPath(path, imageSize.width, imageSize.height);
addBitmapToLruCache(path, bm);
refreashBitmap(bm, imageView, path);
mSemaphoreTHreadPool.release();
}
});
}
}
private void refreashBitmap(Bitmap bm, ImageView imageView, String path) {
Message message = Message.obtain();
ImgBeanHolder holder = new ImgBeanHolder();
holder.bitmap = bm;
holder.imageView = imageView;
holder.path = path;
message.obj = holder;
mUIHandler.sendMessage(message);
}
private void addBitmapToLruCache(String path, Bitmap bm) {
if (getBitmapFromLruCache(path) == null) {
if (bm != null) {
mLruCache.put(path, bm);
}
}
}
private Bitmap decodeSampledBitMapFromPath(String path, int width, int height) {
// 获取图片的宽和高,并不把图片加载到内存中
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = caculateSampleSize(options, width, height);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
private int caculateSampleSize(BitmapFactory.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;
}
protected ImageSize getImageViewSize(ImageView imageView) {
ImageSize imageSize = new ImageSize();
DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
LayoutParams lp = imageView.getLayoutParams();
int width = imageView.getWidth();
if (width <= 0) {
width = lp.width;
}
if (width <= 0) {
// android 16 以上可用
// width = imageView.getMaxWidth();
width = getImageViewFieldValue(imageView, "mMaxWidth");
}
if (width <= 0) {
width = displayMetrics.widthPixels;
}
int height = imageView.getHeight();
if (height <= 0) {
height = lp.height;
}
if (height <= 0) {
height = getImageViewFieldValue(imageView, "mMaxHeight");
}
if (height <= 0) {
height = displayMetrics.heightPixels;
}
imageSize.width = width;
imageSize.height = height;
return imageSize;
}
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
Field field = null;
try {
field = ImageView.class.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
field.setAccessible(true);
try {
int fieldValue = field.getInt(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return value;
}
private synchronized void addTask(Runnable runnable) {
mTaskQueue.add(runnable);
try {
if (mPoolThreadHandler == null)
mSemaphorePoolThreadHandler.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
mPoolThreadHandler.sendEmptyMessage(0X110);
}
private class ImageSize {
int width;
int height;
}
private Bitmap getBitmapFromLruCache(String key) {
return mLruCache.get(key);
}
private class ImgBeanHolder {
ImageView imageView;
Bitmap bitmap;
String path;
}
}
package org.out.naruto.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.LruCache;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @author Hao_S
*/
public class ImageLoader {
private static final String TAG = "ImageLoader";
private static ImageLoader mInstance;
private LruCache<String, Bitmap> mLruCache;
private ExecutorService mThreadPool;
private static final int DEAFUT_THREAD_COUNT = 1;
/**
* 队列调度方式
*/
private Type mType = Type.LIFO;
private LinkedList<Runnable> mTaskQueue;
private Thread mPoolThread;
private Handler mPoolThreadHandler;
private Handler mUIHandler;
private Semaphore mSemaphorePoolThreadHandler = new Semaphore(0);
private Semaphore mSemaphoreTHreadPool;
public enum Type {
LIFO, FIFO
}
private ImageLoader(int mThreadCount, Type type) {
init(mThreadCount, type);
}
private void init(int threadCount, Type type) {
// 后台轮询线程
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) {
e.printStackTrace();
}
}
};
mSemaphorePoolThreadHandler.release();
Looper.loop();
}
};
mPoolThread.start();
int maxMemory = (int) Runtime.getRuntime().maxMemory();// 获取最大可用内存
int cacheMemory = maxMemory / 8; // 设置为最大可用内存的1/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 Runnable getTask() {
if (mType == Type.FIFO) {
return mTaskQueue.removeFirst();
} else {
return mTaskQueue.removeLast();
}
}
/**
* 单例模式
*/
public static ImageLoader getInstance(int threadCount, Type type) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(threadCount, type);
}
}
}
return mInstance;
}
public void loadImage(final String path, final ImageView imageView) {
imageView.setTag(path);
if (mUIHandler == null) {
mUIHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 获取得到的图片,为ImageView回调设置图片
ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
Bitmap bm = holder.bitmap;
ImageView imageView = holder.imageView;
String path = holder.path;
if (imageView.getTag().toString().equals(path)) {
imageView.setImageBitmap(bm);
}
}
};
}
Bitmap bm = getBitmapFromLruCache(path);
if (bm != null) {
refreashBitmap(bm, imageView, path);
} else {
addTask(new Runnable() {
@Override
public void run() {
// 加载图片 压缩
ImageSize imageSize = getImageViewSize(imageView);
Bitmap bm = decodeSampledBitMapFromPath(path, imageSize.width, imageSize.height);
addBitmapToLruCache(path, bm);
refreashBitmap(bm, imageView, path);
mSemaphoreTHreadPool.release();
}
});
}
}
private void refreashBitmap(Bitmap bm, ImageView imageView, String path) {
Message message = Message.obtain();
ImgBeanHolder holder = new ImgBeanHolder();
holder.bitmap = bm;
holder.imageView = imageView;
holder.path = path;
message.obj = holder;
mUIHandler.sendMessage(message);
}
private void addBitmapToLruCache(String path, Bitmap bm) {
if (getBitmapFromLruCache(path) == null) {
if (bm != null) {
mLruCache.put(path, bm);
}
}
}
private Bitmap decodeSampledBitMapFromPath(String path, int width, int height) {
// 获取图片的宽和高,并不把图片加载到内存中
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = caculateSampleSize(options, width, height);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
private int caculateSampleSize(BitmapFactory.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;
}
protected ImageSize getImageViewSize(ImageView imageView) {
ImageSize imageSize = new ImageSize();
DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
LayoutParams lp = imageView.getLayoutParams();
int width = imageView.getWidth();
if (width <= 0) {
width = lp.width;
}
if (width <= 0) {
// android 16 以上可用
// width = imageView.getMaxWidth();
width = getImageViewFieldValue(imageView, "mMaxWidth");
}
if (width <= 0) {
width = displayMetrics.widthPixels;
}
int height = imageView.getHeight();
if (height <= 0) {
height = lp.height;
}
if (height <= 0) {
height = getImageViewFieldValue(imageView, "mMaxHeight");
}
if (height <= 0) {
height = displayMetrics.heightPixels;
}
imageSize.width = width;
imageSize.height = height;
return imageSize;
}
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
Field field = null;
try {
field = ImageView.class.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
field.setAccessible(true);
try {
int fieldValue = field.getInt(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return value;
}
private synchronized void addTask(Runnable runnable) {
mTaskQueue.add(runnable);
try {
if (mPoolThreadHandler == null)
mSemaphorePoolThreadHandler.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
mPoolThreadHandler.sendEmptyMessage(0X110);
}
private class ImageSize {
int width;
int height;
}
private Bitmap getBitmapFromLruCache(String key) {
return mLruCache.get(key);
}
private class ImgBeanHolder {
ImageView imageView;
Bitmap bitmap;
String path;
}
}