Android线程池异步加载图片,可LIFO,可FIFO加载,四级缓存

功能:  线程池异步加载图片(对图片进行压缩,内存控制)

              可LIFO加载(后进先出)快速滑动时,优先加载当前页面显示的图片,防止等待

              可FIFO加载(先进先出)

              四级缓存(内存缓存,磁盘缓存,文件缓存,网络缓存)


项目地址:https://github.com/AndroidCloud/LIFOandFIFOImageLoader   如有不足,欢迎各位issues和开支优化

GitHub地址:https://github.com/AndroidCloud

最终实现效果:

                                            

技术路线(简要技术思路,具体实现详见GitHub的Demo):

1,LIFO和FIFO线程池

     首先是加载方式

//两种加载方式,先进先出和先进后出

public enum  LoadType {
    FIFO, LIFO
}
      其次,封装ImageRunnable对象,实现线程优先级接口,此处优先级使用加入任务队列的时间来判断。

     LIFO为任务时间迟的优先进行,FIFO为任务时间早的优先进行

public class ImageRunnable implements Runnable,Comparable<ImageRunnable>{
    @Override
    public int compareTo(ImageRunnable ImageRunnable) {
        long my = this.getPriority();
        long other = ImageRunnable.getPriority();
        if (loadType==null)return (int)(other-my);
        if (loadType.equals(LoadType.LIFO)){
            return (int)(other-my);
        }else {
            return (int)(my-other);
        }
    }
    //其余代码省略
        最后是线程池,此处控制线程数为可运行的处理器个数+1

private void initThreadPool() {
    int number=Runtime.getRuntime().availableProcessors()+1;
    imgThreadPool = new ThreadPoolExecutor(number, number, 0L, TimeUnit.SECONDS, new PriorityBlockingQueue<Runnable>());
}
2,缓存处理

      首先是内存缓存的处理,此处取可用缓存的1/8来做缓存。

public class LruCacheHelper {
    private static LruCache<String, Bitmap> memCache;
    public LruCacheHelper(){
        initMemCache();
    }
    /**
     * 初始化内存缓存
     */
    public void initMemCache() {
        int maxMemory = (int) (Runtime.getRuntime().maxMemory()/1024);
        int cacheSize = maxMemory / 8;
        memCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount()/1024;
            }
        };
    }
    /**
     * 从内存缓存中拿
     */
    public  Bitmap getBitmapFromMem(String url) {
        return memCache.get(url);
    }

    /**
     * 加入到内存缓存中
     */
    public  void putBitmapToMem(String url, Bitmap bitmap) {
        if (getBitmapFromMem(url) == null)
        {
            if (bitmap != null){
                memCache.put(url, bitmap);
            }
        }
    }
    /**删除某个缓存*/
    public void removeBitmapFromMem(String url){
        memCache.remove(url);
    }
    /**删除全部缓存*/
    public void removeBitmapAll(){
        memCache.evictAll();
    }
}
      其次是磁盘缓存,此处默认磁盘缓存最大为100M,如果有SD卡则存在SD卡,没有则存在自带内存中

public class DiskLruCacheHelper {
    private  DiskLruCache diskLruCache;
    private   int DISK_CACHE_DEFAULT_SIZE = 100 * 1024 * 1024;// 文件缓存默认 100M
    public DiskLruCacheHelper(){
        initDiskLruCache();
    }
    /**
     * 初始化磁盘缓存
     */
    public void initDiskLruCache() {
        try {
            File cacheDir = getDiskCacheDir(MyApplication.context, "vmeet_bitmap");
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            diskLruCache = DiskLruCache.open(cacheDir, getAppVersion(MyApplication.context), 1, DISK_CACHE_DEFAULT_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public   File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }
    public  int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }
    public  String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    public  String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
    /**
     * 从磁盘缓存中拿
     */
    public  Bitmap getBitmapFromDisk(String url) {
        try {
            String key = hashKeyForDisk(url);
            DiskLruCache.Snapshot snapShot = diskLruCache.get(key);
            if (snapShot != null) {
                InputStream is = snapShot.getInputStream(0);
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                return bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**加入磁盘缓存*/
    public void putBitmapToDiskMem(String url,Bitmap bitmap){
        try {
            String key = hashKeyForDisk(url);
            DiskLruCache.Editor editor = diskLruCache.edit(key);
            if (editor != null) {
                OutputStream outputStream = editor.newOutputStream(0);
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
                editor.commit();
            }
            diskLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**删除某个磁盘缓存*/
    public void removeBitmapFromDiskMem(String url){
        try {
            String key = hashKeyForDisk(url);
            diskLruCache.remove(key);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**删除全部磁盘缓存*/
    public void removeBitmapAll(){
        try {
            diskLruCache.delete();
            /**重新初始化次磁盘缓存*/
            initDiskLruCache();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public long getSize(){
        return diskLruCache.size();
    }
}
3,图片压缩,压缩过程为:得到ImageView的大小,再取到图片大小,计算出需要压缩的比例,然后进行压缩,得到大小合适的Bitmap,放入缓存中。防止Bitmap占用过多缓存。

public class ImageHelper {
    public ImageHelper(){

    }
    /**取到图片大小,传入ImageView的大小,按照该大小进行压缩*/
    public  Bitmap getLocalImg(String path,int width, int height){
        File file=new File(path);
        if (file.exists()){
            // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(path, options);
            // 调用上面定义的方法计算inSampleSize            options.inSampleSize = calculateInSampleSize(options, width,
                    height);
            // 使用获取到的inSampleSize值再次解析图片
            options.inJustDecodeBounds = false;
            Bitmap bitmap = BitmapFactory.decodeFile(path, options);
            return bitmap;
        }else{
            return null;
        }
    }
    /**计算图片压缩大小*/
    public   int calculateInSampleSize(BitmapFactory.Options options,
                                            int reqWidth, int reqHeight)
    {
        // 源图片的宽度
        int width = options.outWidth;
        int height = options.outHeight;
        int inSampleSize = 1;

        if (width > reqWidth && height > reqHeight)
        {
            // 计算出实际宽度和目标宽度的比率
            int widthRatio = Math.round((float) width / (float) reqWidth);
            int heightRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = Math.max(widthRatio, heightRatio);
        }
        return inSampleSize;
    }
    /**反射得到ImageView的大小*/
    public  ImgSize getImageViewSize(ImageView imageView)
    {
        ImgSize imageSize = new ImgSize();
        final DisplayMetrics displayMetrics = imageView.getContext()
                .getResources().getDisplayMetrics();
        final ViewGroup.LayoutParams params = imageView.getLayoutParams();

        int width = params.width == ViewGroup.LayoutParams.WRAP_CONTENT ? 0 : imageView
                .getWidth(); // Get actual image width
        if (width <= 0)
            width = params.width; // Get layout width parameter
        if (width <= 0)
            width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check
        // maxWidth
        // parameter
        if (width <= 0)
            width = displayMetrics.widthPixels;
        int height = params.height == ViewGroup.LayoutParams.WRAP_CONTENT ? 0 : imageView
                .getHeight(); // Get actual image height
        if (height <= 0)
            height = params.height; // Get layout height parameter
        if (height <= 0)
            height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check
        // maxHeight
        // parameter
        if (height <= 0)
            height = displayMetrics.heightPixels;
        imageSize.setWidth(width);
        imageSize.setHeight(height);
        return imageSize;

    }
    public  int getImageViewFieldValue(Object object, String fieldName)
    {
        int value = 0;
        try
        {
            Field field = ImageView.class.getDeclaredField(fieldName);
            field.setAccessible(true);
            int fieldValue = (Integer) field.get(object);
            if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE)
            {
                value = fieldValue;
                Log.e("TAG", value + "");
            }
        } catch (Exception e)
        {
        }
        return value;
    }
}

4,图片加载流程

      加载时使用Handler进行线程通信,通信介质为ImgViewBean,加载过程为内存缓存→磁盘缓存→文件缓存        →           网络资源(实现四级缓存)

public class ImgViewBean {
    public ImageView imageView;//对应的ImageView
    public String url;//图片的地址
    public Bitmap bitmap;//用来显示的Bitmap

public class ImageRunnable implements Runnable,Comparable<ImageRunnable>{
    @Override
    public void run() {
        Bitmap bitmap;
        bitmap=lruCacheHelper.getBitmapFromMem(url);
        if (bitmap!=null){
            sendImgToUiThread(bitmap);
        }else{
            bitmap=diskLruCacheHelper.getBitmapFromDisk(url);
            if (bitmap!=null){
                lruCacheHelper.putBitmapToMem(url, bitmap);
                sendImgToUiThread(bitmap);
            }else{
                ImgSize imgSize= imageHelper.getImageViewSize(imageView);
                bitmap= imageHelper.getLocalImg(url, imgSize.getWidth(), imgSize.getHeight());
                if (bitmap!=null){
                    lruCacheHelper.putBitmapToMem(url, bitmap);
                    diskLruCacheHelper.putBitmapToDiskMem(url,bitmap);
                    sendImgToUiThread(bitmap);
                }else {
                    //去网络上加载
                }
            }
        }
    }
    /**Bitmap发送到UI线程*/
    private void sendImgToUiThread(Bitmap bitmap) {
        ImgViewBean imgViewBean=new ImgViewBean(imageView,url,bitmap);
        Message message = Message.obtain();
        message.obj = imgViewBean;
        handler.sendMessage(message);
    }
    //其余代码省略
         UI线程刷新界面,为防止错位,使用图片的Path作为Tag

handler=new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        //显示图片,防止错位
        ImgViewBean imgViewBean=(ImgViewBean)msg.obj;
        if (imgViewBean.getImageView().getTag().toString().equals(imgViewBean.getUrl())){
            imgViewBean.getImageView().setImageBitmap(imgViewBean.getBitmap());
        }
    }
};
5,准备工作就绪,封装图片加载类,留出加载接口。

      首先是保证单例模式,此处采用懒汉模式

public class ImageShowUtil {
    private static ImageShowUtil imageUtil;
    private  ExecutorService imgThreadPool;/**加载图片线程池*/
    private Handler handler;/**线程间通信*/
    private ImageHelper imageHelper;/**加载图片帮助类*/
    private LruCacheHelper lruCacheHelper;/**内存缓存帮助类*/
    private DiskLruCacheHelper diskLruCacheHelper;/**磁盘缓存帮助类*/
    private ImageShowUtil(){
        initThreadPool();
        imageHelper=new ImageHelper();
        lruCacheHelper=new LruCacheHelper();
        diskLruCacheHelper=new DiskLruCacheHelper();
        handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //显示图片,防止错位
                ImgViewBean imgViewBean=(ImgViewBean)msg.obj;
                if (imgViewBean.getImageView().getTag().toString().equals(imgViewBean.getUrl())){
                    imgViewBean.getImageView().setImageBitmap(imgViewBean.getBitmap());
                }
            }
        };
    }
    private void initThreadPool() {
        int number=Runtime.getRuntime().availableProcessors()+1;
        imgThreadPool = new ThreadPoolExecutor(number, number, 0L, TimeUnit.SECONDS, new PriorityBlockingQueue<Runnable>());
    }
    /**单例模式*/
    public static ImageShowUtil getInstance(){
        if (imageUtil == null) {
            synchronized (ImageShowUtil.class) {
                if (imageUtil == null) {
                    imageUtil = new ImageShowUtil();
                }
            }
        }
        return imageUtil;
    }
          其次是图片加载方法,初步展示4中加载方式

/**加载本地图片指定了任务队列的加载方式,且使用占位图*/
public void loadImg(LoadType loadType,String path,ImageView imageView,int placeHolder){
    if (imageView.getTag()==null){
        imageView.setTag(path);
        imageView.setImageResource(placeHolder);
    }else if (!imageView.getTag().toString().equals(path)) {
        imageView.setTag(path);
        imageView.setImageResource(placeHolder);
    }
    imgThreadPool.execute(new ImageRunnable(loadType, imageView, path, handler, imageHelper, lruCacheHelper, diskLruCacheHelper));
}
/**加载本地图片未指定任务队列的加载方式(默认LIFO),且使用占位图*/
public void loadImg(String path,ImageView imageView,int placeHolder){
    if (imageView.getTag()==null){
        imageView.setTag(path);
        imageView.setImageResource(placeHolder);
    }else if (!imageView.getTag().toString().equals(path)) {
        imageView.setTag(path);
        imageView.setImageResource(placeHolder);
    }
        imgThreadPool.execute(new ImageRunnable(imageView, path,handler,imageHelper,lruCacheHelper,diskLruCacheHelper));
}
/**加载本地图片指定了任务队列的加载方式,未使用占位图*/
public void loadImg(LoadType loadType,String path,ImageView imageView){
    if (imageView.getTag()==null){
        imageView.setTag(path);
    }else if (!imageView.getTag().toString().equals(path)) {
        imageView.setTag(path);
    }
    imgThreadPool.execute(new ImageRunnable(loadType,imageView, path,handler,imageHelper, lruCacheHelper,diskLruCacheHelper));
}
/**加载本地图片未指定任务队列的加载方式(默认LIFO),未使用占位图*/
public void loadImg(String path,ImageView imageView){
    if (imageView.getTag()==null){
        imageView.setTag(path);
    }else if (!imageView.getTag().toString().equals(path)) {
        imageView.setTag(path);
    }
    imgThreadPool.execute(new ImageRunnable(imageView, path,handler,imageHelper,lruCacheHelper,diskLruCacheHelper));
}

6,在ListView中使用示例,在Adapter的getView方法中使用

ImageShowUtil.getInstance().loadImg(list.get(i),viewHolder.iv_listview,R.drawable.b);


                              做开发,需要脚踏实地,日积月累,愿你我共勉

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章