复习图片加载ImageLoader

距离上一次视频学习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;
}
}
  • 如果注解有不正确的话,欢迎指正哈。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值