android开发之就本地和网络图片的压缩以及缓存详解

一、图片的压缩

1、本地图片的压缩

无论是本地亦或者是网络来源图片,加载都需要进行合适的压缩,然后通过控件显示出来。

第一步:获得ImageView控件想要显示的大小

/**
 * Created by ZaneLove on 2015/3/15.
 */
public class ImageSizeUtil {
    /**
     * a.根据ImageView获得适应的压缩的宽和高
     * @param imageView
     * @return
     */
    public static ImageSize getImageViewSize(ImageView imageView){
        ImageSize imageSize = new ImageSize();
        //获取ImageView控件的宽和高
        DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
        ViewGroup.LayoutParams lp = imageView.getLayoutParams();

        // 获取imageview的实际宽度
        int width = imageView.getWidth();
        //首先企图通过getWidth获取显示的宽,如果返回0;那么再去看看它有没有在布局文件中声明宽
        if(width <= 0) {
            width = lp.width;
        }
        //如果布局文件中也没有精确值,那么我们再去看看它有没有设置最大值
        /**
         * 可以看到这里或者最大宽度,我们用的反射,而不是getMaxWidth();为啥呢,因为getMaxWidth竟然要API 16,我也是醉了;为了兼容性,我们采用反射的方案。
         */
        if(width <= 0 ) {
            width = getImageViewFieldValue(imageView,"mMaxWidth");
        }
        //如果最大值也没设置,那么我们只有拿出我们的终极方案,使用我们的屏幕宽度
        if(width <=0) {
            width = displayMetrics.widthPixels;
        }

        // 获取imageview的实际高度
        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控件的某个属性值
     * @param object
     * @param fieldName
     * @return
     */
    private static int getImageViewFieldValue(Object object, String fieldName) {
        int value = 0;
        try{
            //Field:属性
            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) {
            e.printStackTrace();
        }
        return value;
    }

    public static class ImageSize {
        public int width;
        public int height;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

第二步:设置合适的inSampleSize 
上面,我们获得了图片想要显示的宽和高,那么,接下来就是拿着宽和高与图片真正的宽和高做比较,得到一个合适的inSampleSize,再去对图片进行压缩。

①、拿到图片真正的宽和高,并存在options里面

//获的图片真正的宽和高,并不把图片加载到内存中
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(),R.drawable.v,options);
//BitmapFactory.decodeFile(path, options);从sd卡中获取图片
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

②、获得inSampleSize

public class ImageSizeUtil {
    /**
     * b.根据需求的宽和高以及图片实际的宽和高计算SampleSize,为了对图片进行压缩
     * @param options
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static int caculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight) {
        /**
         * options里面存了实际的宽和高;reqWidth和reqHeight就是我们之前得到的想要显示的大小;经过比较,得到一个合适的inSampleSize
         */

        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;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

options里面存了实际的宽和高;reqWidth和reqHeight就是我们之前得到的想要显示的大小;经过比较,得到一个合适的inSampleSize。

③、对图片进行压缩

//根据ImageView获得适应的压缩的宽和高
ImageSizeUtil.ImageSize imageSize = ImageSizeUtil.getImageViewSize(iv);
//根据需求的宽和高以及图片实际的宽和高计算SampleSize,为了对图片进行压缩
options.inSampleSize = ImageSizeUtil.caculateInSampleSize(options,imageSize.width,imageSize.height);

//使用获得到的inSampleSize,再次解析图片并压缩图片
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.v, options);
//BitmapFactory.decodeFile(path, options);从sd卡中获取图片
iv.setImageBitmap(bitmap);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2、网络图片的压缩

方案:

a、直接下载存到sd卡,然后采用本地的压缩方案。这种方式当前是在硬盘缓存开启的情况下,如果没有开启呢?

b、使用BitmapFactory.decodeStream(is, null, opts);

/**
 * Created by ZaneLove on 2015/3/15.
 */
public class DownloadImgUtil {
    /**
     * 根据url下载图片在指定的文件
     * @param urlStr
     * @param imageView
     * @return
     */
    public static Bitmap downloadImageByUrl(String urlStr,ImageView imageView) {
        InputStream is = null;

        try {
            //网络图片地址
            URL url = new URL(urlStr);
            //网络连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            //管道输入流
            is = new BufferedInputStream(conn.getInputStream());
            /**
             * BufferedInputStream类调用mark(int readlimit)方法后读取多少字节标记才失效,是取readlimit和BufferedInputStream类的缓冲区大小两者中的最大值,而并非完全由readlimit确定。这个在JAVA文档中是没有提到的。
             */
            is.mark(is.available());

            //图片压缩
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);

            //获得ImageView控件想要显示的宽和高
            ImageSizeUtil.ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView);
            options.inSampleSize = ImageSizeUtil.caculateInSampleSize(options,imageSize.width,imageSize.height);
            options.inJustDecodeBounds = false;
            is.reset();
            bitmap = BitmapFactory.decodeStream(is,null,options);

            conn.disconnect();
            return bitmap;
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            try{
                if(is != null) {
                    is.close();
                }
            }catch (Exception e) {

            }
        }
        return null;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
/**
  * 下载网络图片,并将图片显示在控件上
  */
Bitmap bitmap1 =  DownloadImgUtil.downloadImageByUrl("https://img-my.csdn.net/uploads/201407/26/1406383299_1976.jpg", iv);
iv.setImageBitmap(bitmap1);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

在此,图片压缩已结束,那么,就应该放入LruCache,然后再设置到我们的ImageView上 。

二、图片的缓存

基于此,我将带领大家一起打造一个从压缩到缓存的图片加载框架的架构

打造一个从压缩到缓存的图片加载框架的架构

基本思路 
1、单例模式,包含一个LruCache用来管理缓存图片。

2、任务队列,每来一次加载图片的请求,就封装成Task存入到TaskQueue中。

3、包含一个后台线程,这个线程在第一次初始化实例的时候启动,然后会一直在后台运行,它用来干什么呢?大伙还记得我们有个任务队列么,有队列任务,得有人干活呀,所以,当每来一次加载图片请求的时候,我们同时发一个消息到后台线程,后台线程去使用线程池去TaskQueue去取一个任务来执行。

4、调度策略,在3中说过,后台线程去TaskQueue去取一个任务,这个任务不是随便取的,有策略可以选择,一个是FIFO(先进先出),一个是LIFO(后进先出),我更倾向于后者。

让我们一步两步分析代码如何实现吧!!

1、构造方法 – 使用单例模式
/**
 * 单例模式
 */
private static ImageLoader instance = null;

private ImageLoader(int threadCount,Type type){
    init(threadCount,type);
}

public static ImageLoader getInstance(int threadCount,Type type) {
    if(instance == null) {
        synchronized (ImageLoader.class) {
            if(instance == null) {
                instance = new ImageLoader(threadCount, type);
                return  instance;
            }
        }
    }
    return instance;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

接下来, 让我们一起来看构造函数当中init()方法:(附带全局变量)

1、初始化后台轮询线程 
2、获得应用最大可用内存的1/8,分配给LruCache 
3、创建线程池

//线程数量
private int threadCount;
//队列调度方式,默认为LOFI(后进先出)
private Type mType = Type.LIFO;
//图片缓存的核心对象
private LruCache<String,Bitmap> mLruCache;
//线程池
private ExecutorService mThreadPool;
private static final int DEAFULT_THREAD_COUNT = 1;
//任务队列
private LinkedList<Runnable> mTaskQueue;

private Semaphore mSemaphorePoolThreadHandler = new Semaphore(0);
private Semaphore mSemaphoreThreadPool;

//后台轮询线程
private Thread mPoolThread;
private Handler mPoolThreadHandler;

private Runnable task;

//UI线程中的Handler
private Handler mUIHandler;
//是否开启硬盘缓存
private boolean isDiskCacheEnable = true;

public enum Type {
    FIFO, //先进先出
    LIFO  //后进先出
}

/**
 * 初始化
 * @param threadCount 线程数量
 * @param type 队列调度方式
 */
private void init(int threadCount, Type type) {
    //初始化后台轮询线程
    initBackThread();
    //获得应用最大可用内存的1/8,分配给LruCache
    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);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
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 (Exception e) {
                    }
                }
            };
            //释放一个信号量
            mSemaphorePoolThreadHandler.release();
            //开启轮询器
            Looper.loop();
        }
    };
    //开启线程
    mPoolThread.start();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
/**
 * 从任务队列取出一个任务
 * @return
 */
public Runnable getTask() {
    if(mType == Type.FIFO) {
        return mTaskQueue.removeFirst();
    }else if(mType == Type.LIFO) {
        return mTaskQueue.removeLast();
    }
    return null;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

让我们在好好的分析分析一下以上代码,它到底做了什么?

  1. 在构造方法中我们调用init()方法

  2. 在init()方法中:

    1. 开启后台加载图片线程 – initBackThread(),实际上是让Looper轮询器不断的loop,随时往消息队列中拿消息进行处理
    2. 初始化mPoolThreadHandler用于发送消息到此线程
    3. 初始化LruCache缓存机制,并分配了缓存空间
    4. 创建线程池、初始化任务队列(TaskQueue),使用线程池去TaskQueue中去取一个任务执行

目前为止,我们轮询器开启了、消息队列有了、处理消息的Handler处理器也有了、LruCache缓存机制又随时待命中、最后连线程池都有了,简直就是万事俱备,只欠东风。

2、加载图片(本地 + 网络)
/**
 * 根据path为ImageView设置图片
 */
public void loadImage(final String path,final ImageView imageView,final boolean isFromNet) {
    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;
                //将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 {
        //没有找到图片
        //通过buildTask去新建一个任务,再addTask到任务队列
        addTask(buildTask(path,imageView,isFromNet));
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
/**
* 根据path从缓存当中获取bitmap
* @param key
* @return
*/
private Bitmap getBitmapFromLruCache(String key) {
   return mLruCache.get(key);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
/**
* 去设置图片显示在控件上
* @param path
* @param imageView
* @param bm
*/
private void refreashBitmap(String path, 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);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
/**
 * 新建一个任务
 * @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()) {
                    bm = loadImageFromLocal(file.getAbsolutePath(), imageView);
                } else {
                    // 检测是否开启硬盘缓存
                    if (isDiskCacheEnable) {
                        boolean downloadState = DownloadImgUtil.downloadImgByUrl(path, file);
                        // 如果下载成功
                        if (downloadState) {
                            bm = loadImageFromLocal(file.getAbsolutePath(), imageView);
                        }
                    } else {// 直接从网络加载
                        bm = DownloadImgUtil.downloadImageByUrl(path,imageView);
                    }
                }
            } else {
                bm = loadImageFromLocal(path, imageView);
            }
            // 把图片加入到缓存
            addBitmapToLruCache(path, bm);
            refreashBitmap(path, imageView, bm);
            mSemaphoreThreadPool.release();
        }
    };
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
/**
 * 添加任务到队列
 * @param runnable
 */
private synchronized void addTask(Runnable runnable) {
    mTaskQueue.add(runnable);
    try{
        if(mPoolThreadHandler == null) {
            mSemaphorePoolThreadHandler.acquire();
        }
    }catch (Exception e) {

    }
    mPoolThreadHandler.sendEmptyMessage(0x110);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
/**
 * 获得缓存图片的地址
 * @param context
 * @param mdStr
 * @return
 */
private File getDiskCacheDir(Context context, String mdStr) {
    String cachePath;
    if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
        cachePath = context.getExternalCacheDir().getPath();
        isDiskCacheEnable = true;
    }else {
        cachePath = context.getCacheDir().getPath();
        isDiskCacheEnable = false;
    }
    return new File(cachePath + File.separator + mdStr);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
/**
* 从本地缓存文件中加载图片
* @param path
* @param imageView
* @return
*/
private Bitmap loadImageFromLocal(String path, ImageView imageView) {
   Bitmap bm;
   //加载图片
   //图片压缩
   //1、获得图片需要显示的大小
   ImageSizeUtil.ImageSize imageSize = ImageSizeUtil.getImageViewSize(imageView);
   //2、压缩图片
   bm = decodeSampledBitmapFromPath(path,imageSize.width,imageSize.height);
   return bm;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
/**
* 将图片加入LruCache
* @param path
* @param bm
*/
private void addBitmapToLruCache(String path, Bitmap bm) {
   if(getBitmapFromLruCache(path) == null) {
       if(bm != null) {
           mLruCache.put(path,bm);
       }
   }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

分析以上代码:

  1. 在外界调用loadImage()方法时,传入路径、展示控件(ImageView)和是否是来自于网络
  2. 在loadImage()方法中: 
    1. 为ImageView.setTag
    2. 初始化一个mUIHandler,用于用户更新ImageView控件,因此这个方法肯定是主线程调用的
    3. 首先根据path从缓存当中获取bitmap 
      1. 如果缓存当中找到了该图片,调用refreshBitmap()–设置图片显示在控件上
      2. 如果缓存当中没有找到该图片,通过buildTask()去新建一个任务,再addTask()到任务队列当中 
        1. buildTask()方法中: 
          1. 判断是否来自网络 
            1. 如果来自网络 
              1. 获得缓存图片的地址
              2. 如果在缓存文件中发现,那就说明还在本地,就调用loadImageFromLocal()方法,从本地缓存文件当中加载图片
              3. 如果没有发现 
                1. 检测是否开启硬盘缓存 
                  1. 如果开启了硬盘缓存,从网络下载图片,并存储到硬盘当中,这样的话又是本地缓存了,然后调用localImageFromLocal()从缓存文件中加载图片
                  2. 否则,直接从网络加载
            2. 如果来自本地,那么就直接调用loadImageFromLocal()从本地缓存文件中加载图片即可
          2. 将新加载过来的图片加入到缓存机制当中
          3. 设置图片显示在控件上

PS:就是在代码中,你会看到一些信号量的身影:

第一个:mSemaphorePoolThreadHandler = new Semaphore(0); 用于控制我们的mPoolThreadHandler的初始化完成,我们在使用mPoolThreadHandler会进行判空,如果为null,会通过mSemaphorePoolThreadHandler.acquire()进行阻塞;当mPoolThreadHandler初始化结束,我们会调用.release();解除阻塞。

第二个:mSemaphoreThreadPool = new Semaphore(threadCount);这个信号量的数量和我们加载图片的线程个数一致;每取一个任务去执行,我们会让信号量减一;每完成一个任务,会让信号量+1,再去取任务;目的是什么呢?为什么当我们的任务到来时,如果此时在没有空闲线程,任务则一直添加到TaskQueue中,当线程完成任务,可以根据策略去TaskQueue中去取任务,只有这样,我们的LIFO才有意义。

到此为止,布局我就buCopy了,大伙需要的话就去下载源码吧!嘎嘎,好坏好坏滴那种!

示例代码戳Here



转自:http://blog.csdn.net/zanelove/article/details/44278783

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值