距离上一次视频学习imageloader已经过去两个月了,对图片加载的具体已经忘了七七八八了,因为图片加载所包含的东西比较多,所以最近重新复习一遍,将附带解释的代码放上来方便以后自己复习。
public class ImageLoader {
//实例
private static ImageLoader mInstance;
/*
图片缓存的核心对象
*/
private LruCache<String,Bitmap> mLruCache;
/*
线程池
*/
private ExecutorService mThreadPool;
private static 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 mSemaphoremPoolThreadHandler = new Semaphore(0);//其实设置为0后是可以release的,然后就可以acquire.
//这里设置为0,就是一开始addtask方法阻塞
//等待mPoolThreadHandler对象新建完毕,防止addtask方法中mPoolThreadHandler.sendEmptyMessage()这句话出现空指针。
private Semaphore mSemaphoremThreadPool;
//线程池中的信号量,下面定义为3个,即最多只能同时进行3个线程执行。
public enum Type{
FIFO,LIFO;
}
private ImageLoader(int threadCount,Type type){
init(threadCount,type);
}
private void init(int threadCount, Type type) {
mPoolThread = new Thread(){
@Override
public void run() {
Looper.prepare();//在非主线程中使用new handler()时要创建LOOPER对象,需要先调用Looper.prepare()启用looper
mPoolThreadHandler = new Handler(){
public void handleMessage(Message msg){
//该线程池去取出一个任务执行
mThreadPool.execute(getTask());
try {
mSemaphoremThreadPool.acquire();//线程池信号量需要一个
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
mSemaphoremPoolThreadHandler.release();//释放一个信号量使并行的addtask方法可以往下执行。
Looper.loop();//让Looper开始工作,从消息队列里取消息,处理消息。
}
};
mPoolThread.start();
//获取我们应用的最大可用内存
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();
//value.getRowBytes()用于计算位图每一行所占用的内存字节数,和行数相乘,代表获得图片的大小
}
};
//创建线程池
mThreadPool = Executors.newFixedThreadPool(threadCount);
//此时如果有新的线程要建立,只能放在另外的队列中等待
//直到当前的线程中某个线程终止直接被移出池子
mTaskQueue = new LinkedList<Runnable>();
mType = type;
mSemaphoremThreadPool = new Semaphore(threadCount);
}
//获取任务
private Runnable getTask(){
if(mType==Type.FIFO){
//如果mType是先进先出的话,取队列的第一个
return mTaskQueue.removeFirst();
}
else if(mType==Type.LIFO)
//如果mType是后进先出的话,取队列的最后一个
return mTaskQueue.removeLast();
return null;
}
public static ImageLoader getInstance(int threadcount,Type type){
if(mInstance==null){
//比如有两个线程进入到这里,然后如果不进行双重判断的话,就会有几个mInstance先后顺序被初始化。
//如果加入了双重判断的化,就会只新建一个mInstance。
synchronized (ImageLoader.class){
if(mInstance==null){
mInstance = new ImageLoader(DEAFULT_THREAD_COUNT,Type.LIFO);
}
}
}
return mInstance;
}
public void loadImage(final String path, final ImageView imageView){
imageView.setTag(path);//将路径设置为图片的标签,防止混乱
//mUIHandler只创建一次
if(mUIHandler == null){
mUIHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//获取到图片,为imageview回调设置图片
ImageBeanHoader holder = (ImageBeanHoader) 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);
//如果缓存中有,直接从缓存拿,不过大部分都是执行下面的else方法
if(bm!=null)
{
refreshBitmap(path,imageView,bm);//将获得的图片,路径,imageview发送给UIhandler。因为多次使用,于是包装成一个方法。
}
else{
addTasks(new Runnable(){
@Override
public void run() {
//加载图片
//图片的压缩
//1、获得图片需要显示的大小
ImageSize imageSize = getImageViewSize(imageView);
//2、压缩图片
Bitmap bm = decodeSampleBitmapFromPath(path,imageSize.width,imageSize.height);
//3、把图片加入到缓存
addBitmapToLrucache(path,bm);
refreshBitmap(path, imageView, bm);
mSemaphoremThreadPool.release();
//完成加载,线程池释放一个信号量,等于可以继续加一个线程进来。
}
});
}
}
//将path,imageview,bitmap发送给uihandler让其显示
private void refreshBitmap(String path, ImageView imageView, Bitmap bm) {
Message message = Message.obtain();
ImageBeanHoader holder = new ImageBeanHoader();
holder.bitmap = bm;
holder.path = path;
holder.imageView = imageView;
message.obj = holder;
mUIHandler.sendMessage(message);
}
/*
将图片加入Lrucache
*/
protected void addBitmapToLrucache(String path,Bitmap bm){
if(getBitmapFromLrucache(path) == null){
if(bm!=null)
mLruCache.put(path,bm);
}
}
/*
根据图片需要显示的宽和高对图片进行压缩
*/
protected Bitmap decodeSampleBitmapFromPath(String path,int width,int height){
//获得图片的宽和高
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
//如果将options.injustdecodebounds = true,那么BitmapFactory.decodeFile(String path, Options opt)并不会真的返回一个Bitmap给你,
// 它仅仅会把它的宽,高取回来给你,这样就不会占用太多的内存,也就不会那么频繁的发生OOM了。
BitmapFactory.decodeFile(path,options);
//返回null,就可以通过options.outwidth和options.outheight获取宽和高。
options.inSampleSize = caculateInSampleSize(options,width,height);/*图片长宽方向缩小倍数*/
//使用或得到的InsampleSize再次解析图片
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(path,options);
return bitmap;
}
/*
根据需要的宽和高以及图片实际的宽和高计算samplesize
*/
private int caculateInSampleSize(BitmapFactory.Options options, int reqwidth, int reqheight) {
int width = options.outWidth;//图片的宽度
int height = options.outHeight;//图片的高度
//reqwidth实际需要显示的宽度
//reqheight实际需要显示的高度
int inSampleSize = 1;
if(width>reqwidth||height>reqheight){
int widthRadio = Math.round(width*1.0f/reqwidth);//Math.round,四舍五入
int heightRadio = Math.round(height*1.0f/reqheight);
inSampleSize = Math.max(widthRadio,heightRadio);//获得缩小的倍数
}
return inSampleSize;
}
/*
根据ImageView适当的压缩宽和高
*/
private ImageSize getImageViewSize(ImageView imageView) {
ImageSize imageSize = new ImageSize();//因为要返回宽和高,不能写成两个return,所以将其封装为一个类的属性,方便返回
DisplayMetrics displayMetrics=imageView.getContext().getResources().getDisplayMetrics();
ViewGroup.LayoutParams lp = imageView.getLayoutParams();
int width = imageView.getWidth();
if(width<=0){
width = lp.width;//获取imageview在layout中声明的宽度
}
if(width<=0){
width = getImageViewFieldValue(imageView,"mMaxWidth");//通过反射获取最大宽度,也可使用getMaxWidth,不过仅支持API16或以上。
}
if(width<=0){
width = displayMetrics.widthPixels;
}
int height = imageView.getHeight();
if(height<=0){
height = lp.height;//获取imageview在layout中声明的宽度
}
if(height<=0){
height = getImageViewFieldValue(imageView, "mMaxHeight");//同上width
}
if(height<=0){
height = displayMetrics.heightPixels;
}
imageSize.width = width;
imageSize.height = height;
return imageSize;
}
/**
* 通过反射获取imageview的某个属性值
* @param object
* @param fileName
* @return
*/
private static int getImageViewFieldValue(Object object,String fileName){
int value = 0;
Field field = null;
try {
field = ImageView.class.getDeclaredField(fileName);//返回一个field
field.setAccessible(true);//获取权限访问
int fieldValue = field.getInt(object);
if(fieldValue>0&&fieldValue<Integer.MAX_VALUE){
value = fieldValue;
}
} catch (Exception e) {
}
return value;
}
private class ImageSize{
int width;
int height;
}
private synchronized void addTasks(Runnable runnable) {
mTaskQueue.add(runnable);//将一个线程增加进队列中
try {
if(mPoolThreadHandler ==null)
mSemaphoremPoolThreadHandler.acquire(); //如果该handler为空,则阻塞等待其实例化。
} catch (InterruptedException e) {
e.printStackTrace();
}
mPoolThreadHandler.sendEmptyMessage(0x110);
//通知线程池执行该线程
}
private Bitmap getBitmapFromLrucache(String key) {
return mLruCache.get(key);//从缓存中获取图片
}
//创建ImageBeanHoader是为了防止图片显示错乱,因为所获取的path,bitmap不一定是loadImage()参数的path和bitmap
private class ImageBeanHoader{
Bitmap bitmap;
ImageView imageView;
String path;
}
}
- 如果注解有不正确的话,欢迎指正哈。