Android 实现内存+SD卡 图片缓存策略 (LurCache+DiskLruCache)

以前一直用图片框架,这次因为项目中图片量不多,于是想试试封装一个小型的缓存工具,效果还不错

代码主要来自郭大神的文章Android照片墙完整版,完美结合LruCache和DiskLruCache

主要实现LurCache+DiskLruCache缓存模式,这里先介绍一下这两个类:

LurCache
是系统提供的缓存类,可以将图片直接缓存在其中,只有在程序内存不够的时候,LurCache中的内容才会被释放,这也是相比软引用和弱引用的好处之一,Android2.3以后软引用和弱引用变得很容易被回收

DiskLruCache 通常将数据缓存在 “/sdcard/Android/data//cache” 下,在应用被卸载的时候,目录下的数据将被一起删除
DiskLruCache 不是系统提供过的类,需要下载源码

下面是取自 郭霖 文章的DiskLruCache下载方法

由于DiskLruCache并不是由Google官方编写的,所以这个类并没有被包含在Android API当中,我们需要将这>个类从网上下载下来,然后手动添加到项目当中。DiskLruCache的源码在Google Source上,地址如下:
android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java

如果Google Source打不开的话,也可以点击这里下载DiskLruCache的源码。下载好了源码之后,只需要>在项目中新建一个libcore.io包,然后将DiskLruCache.Java文件复制到这个包中即可。

代码

缓存实现:在得到图片url时,先以url为key,在内存中查看有没有此图片,没有的话再重SD卡缓存目录中查找此图>片,依然没有找到的话,则开启异步任务,将图片下载到SD卡缓存目录,同时添加到内存缓存中;

/**
 * 第一次调用方法getInstance()需要传入Context进行初始化,建议使用ApplicationContext进行初始化,
 * 否则单例会一直持有context导致Activity无法被回收
 * 可以在Apllication中   ImageLoader.getInstance(getApplicationContext());
 */
ImageLoader.getInstance().load(imageView, image_url);

ImageLoader

public class ImageLoader {
    private static ImageLoader mImageLoader;
    ImageCache imageCache;

    /**
     * 构造方法私有化
     */
    private ImageLoader(Context context) {
        imageCache = new ImageCache(context);
    }

    /**
     * 获取单例
     * 这里持有了一个Context 所以切记在第一次使用geInstance(ApplicationContext)来初始化,以免造成内存泄露
     */
    public static ImageLoader getInstance(Context... contexts) {
        if (contexts != null && contexts.length > 0)
            if (mImageLoader == null)
                synchronized (ImageLoader.class) {
                    if (mImageLoader == null)
                        mImageLoader = new ImageLoader(contexts[0]);
                }
        return mImageLoader;
    }


    /**
     * 加载图片
     */
    public void load(final ImageView view, String url) {
        //先查看缓存中有没有图片
        Bitmap bitmap = imageCache.getBitmapFromMemoryCache(imageCache.hashKeyForDisk(url));
        if (bitmap != null)
            view.setImageBitmap(bitmap);
        else {
            //RXjava 异步加载图片
            Observable.just(url)
                    .subscribeOn(Schedulers.io())
                    .observeOn(Schedulers.io())
                    .map(new Func1<String, Bitmap>() {
                        @Override
                        public Bitmap call(String imageUrl) {
                            //在SD卡中查找有没有图片,没有则重新下载
                            return imageCache.loadImage(imageUrl);
                        }
                    })
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Action1<Bitmap>() {
                        @Override
                        public void call(Bitmap bitmap) {
                        //更新ImageView
                            if (bitmap != null)
                                view.setImageBitmap(bitmap);
                        }
                    });
        }
    }
}

ImageCache

public class ImageCache {
    /**
     * 图片在内存中的缓存池,在内存不够时会将近期使用最少的图片回收
     */
    private LruCache<String, Bitmap> mMemoryCache;

    /**
     * 图片硬盘缓存类
     */
    private DiskLruCache mDiskLruCache;


    public ImageCache(Context context) {
        //初始化
        initLruCache();
        initDiskCache(context);
    }


    /**
     * 加载图片
     */
    public Bitmap loadImage(String imageUrl) {
        FileDescriptor fileDescriptor = null;
        FileInputStream fileInputStream = null;
        DiskLruCache.Snapshot snapShot = null;
        try {
            String key = hashKeyForDisk(imageUrl);
            // 查找key对应的缓存
            snapShot = mDiskLruCache.get(key);
            if (snapShot == null) {
                // 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
                DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                if (editor != null) {
                    OutputStream outputStream = editor.newOutputStream(0);
                    if (downloadUrlToStream(imageUrl, outputStream)) {
                        editor.commit();
                    } else {
                        editor.abort();
                    }
                }
                // 缓存被写入后,再次查找key对应的缓存
                snapShot = mDiskLruCache.get(key);
            }
            if (snapShot != null) {
                fileInputStream = (FileInputStream) snapShot.getInputStream(0);
                fileDescriptor = fileInputStream.getFD();
            }
            // 将缓存数据解析成Bitmap对象
            Bitmap bitmap = null;
            if (fileDescriptor != null) {
                bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
            }
            if (bitmap != null) {
                // 将Bitmap对象添加到内存缓存当中
                addBitmapToMemoryCache(key, bitmap);
            }
            return bitmap;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileDescriptor == null && fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                }
            }
        }
        return null;
    }


    /**
     * 建立HTTP请求,并获取Bitmap对象。
     *
     * @param urlString 图片的URL地址
     * @return 解析后的Bitmap对象
     */
    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
            out = new BufferedOutputStream(outputStream, 8 * 1024);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }


    /**
     * 将一张图片存储到LruCache中。
     *
     * @param key    LruCache的键,这里传入图片的URL地址。
     * @param bitmap LruCache的键,这里传入从网络上下载的Bitmap对象。
     */
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemoryCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    /**
     * 从LruCache中获取一张图片,如果不存在就返回null。
     *
     * @param key LruCache的键,这里传入图片的URL地址。
     * @return 对应传入键的Bitmap对象,或者null。
     */
    public Bitmap getBitmapFromMemoryCache(String key) {
        return mMemoryCache.get(key);
    }


    /**
     * 初始化内存缓存
     */
    public void initLruCache() {
        // 获取应用程序最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        // 设置图片缓存大小为程序最大可用内存的1/8
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount();
            }
        };
    }

    /**
     * 初始化硬盘缓存
     */
    public void initDiskCache(Context context) {
        try {
            // 获取图片硬盘缓存路径(Constants.CACHE_DIR是我的常量,这里传入一个文件夹名用于缓存目录就可以了)
            File cacheDir = getDiskCacheDir(context, Constants.CACHE_DIR);
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            // 创建DiskLruCache实例,初始化缓存数据
            mDiskLruCache = DiskLruCache
                    .open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 根据传入的uniqueName获取硬盘缓存的路径地址。
     */
    private File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        //SD卡存在或SD卡为内置不可被移除的时候
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            //获取缓存目录(/sdcard/Android/data/<application package>/cache)
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            //获取应用程序缓存目录,当设备内存不足时,可能会被删除( /data/data/<application package>/cache)
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }


    /**
     * 获取当前应用程序的版本号。
     * 每当版本号改变,缓存路径下存储的所有数据都会被清除掉,DiskLruCache认为当应用程序有版本更新的时候,所有的数据都应该从网上重新获取
     */
    private int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),
                    0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }


    /**
     * 使用MD5算法对传入的key进行加密并返回。
     */
    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;
    }

    private 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();
    }
}

猜你喜欢

转载自blog.csdn.net/Mr_Sk/article/details/65633138
今日推荐