一行代码搞定三级缓存

Android的三级缓存,其中主要的就是内存缓存和硬盘缓存,分别是LruCache和DisLruCache。

LruCache是Android 3.1所提供的一个缓存类,所以在Android中可以直接使用LruCache实现内存缓存。而DisLruCache目前在Android 还不是Android SDK的一部分,但Android官方文档推荐使用该算法来实现硬盘缓存。

简单使用

var listJob = ArrayList()
KotlinCacheUtil.INSTANCE.init(application)
val imageJob = KotlinCacheUtil.INSTANCE.setImageBitmap(
            "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
            imageView,
            R.drawable.ic_launcher
        )
listJob.add(imageJob)

取消协程防止内存泄漏

override fun onDestroy() {
    super.onDestroy()
    for (job in listJob){
        job.cancel()
    }
}

本文使用了Kotlin协程需要导入kotlinx-coroutines,网络请求框架okhttp

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
implementation 'com.squareup.okio:okio:2.2.2'

混淆代码

##okhttp3混淆
-keep class okhttp3.** {*;}
-dontwarn okhttp3.**

##okio混淆
-keep class okio.** {*;}
-dontwarn okio.**

权限

6.0以上使用sd卡需要动态申请权限,自行百度





一、LruCache

不废话直接上工具类,初始化会在另一个管理缓存的工具类中使用,这里不需要操心

public class LruCacheUtil {

    private static volatile LruCacheUtil instance;
    private LruCacheUtil() {}
    public static LruCacheUtil getInstance() {
        if (instance == null) {
            synchronized (LruCacheUtil.class) {
                if (instance == null) {
                    instance = new LruCacheUtil();
                }
            }
        }
        return instance;
    }

    private LruCache lruCache;

    /**
     * 初始化
     */
    public void init() {
        long maxMemory = Runtime.getRuntime().maxMemory();
        int cacheSize = (int) (maxMemory / 8);
        lruCache = new LruCache(cacheSize) {
            @Override
            protected int sizeOf(@NotNull String key, @NotNull Bitmap value) {
                return value.getByteCount();
            }
        };
    }

    /**
     * 把Bitmap对象加入到缓存中
     * @param imageUrl 图片url
     * @param bitmap 加入缓存的图片
     */
    public void addBitmapToMemory(String imageUrl, Bitmap bitmap) {
        String key = Md5Util.getInstance().MD5(imageUrl);
        if (getBitmapFromMemCache(key) == null) {
            lruCache.put(key, bitmap);
        }
    }

    /**
     * 从缓存中得到Bitmap对象
     * @param imageUrl 图片url
     * @return Bitmap
     */
    public Bitmap getBitmapFromMemCache(String imageUrl) {
        String key = Md5Util.getInstance().MD5(imageUrl);
        return lruCache.get(key);
    }

    /**
     * 从缓存中删除指定的Bitmap
     * @param imageUrl 图片url
     */
    public void removeBitmapFromMemory(String imageUrl) {
        String key = Md5Util.getInstance().MD5(imageUrl);
        lruCache.remove(key);
    }

    /**
     * 清除所有缓存
     */
    public void evictAll(){
        lruCache.evictAll();
    }
}

二、DisLruCache

今年Kotlin语言从安卓一等公民升级为第一等公民,是时候重新学习一波Kotlin了,DisLruCache工具类使用了新特性“协程”,没有了解的小伙伴可以先了解一下,用过后都说好;Kotlin的IO流值得吹一波,实在太好用了,inputStream.copyTo(outputStream)一行代码解决从一个流写到另一个流;最后吹一波Kotlin的单例模式,实在太好用了。

不要忘了这个

implementation 'com.jakewharton:disklrucache:2.0.2'

工具类

enum class DiskLruCacheUtil {

    INSTANCE;

    private lateinit var application: Application
    private lateinit var diskLruCache: DiskLruCache

    fun init(application: Application) {
        this.application = application
        try {
            diskLruCache = DiskLruCache.open(directory("bitmap"), getAppVersion(), 1, (10 * 1024 * 1024).toLong())
        } catch (e: IOException) {
            e.printStackTrace()
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
    }

    /**
     * 把Bitmap对象加入到缓存中
     * @param imageUrl 图片url,如https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg
     * @param bitmap 加入缓存的图片
     */
    fun addBitmapToMemory(imageUrl: String){
        try {
            val key = Md5Util.getInstance().MD5(imageUrl)
            val editor = diskLruCache.edit(key)
            if (editor != null) {
                val outputStream = editor.newOutputStream(0)
                if (downloadUrlStream(imageUrl, outputStream)){
                    editor.commit()
                }else{
                    editor.abort()
                }
            }
            diskLruCache.flush()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    /**
     * 网络下载
     * @param imageUrl 图片url
     * @param outputStream diskLruCache的IO流
     * @return 是否下载成功
     */
    private fun downloadUrlStream(imageUrl: String, outputStream: OutputStream) :Boolean{
        val request = Request.Builder()
            .url(imageUrl)
            .build()

        try {
            val response = HttpUtil.getInstance().client.newCall(request).execute()
            if (response.isSuccessful) {
                if (response.body()!= null){
                    val bis = response.body()!!.byteStream().buffered()
                    val bos = outputStream.buffered()
                    try {
                        bis.copyTo(bos)
                        return true
                    }catch (e: IOException) {
                        e.printStackTrace()
                    }finally {
                        bis.close()
                        bos.close()
                    }
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return false
    }

    /**
     * 从缓存中得到Bitmap对象
     * @param imageUrl 图片url
     * @return Bitmap
     */
    fun getBitmapFromMemCache(imageUrl: String,maxWidth:Int,maxHeight:Int): Bitmap? {
        val key = Md5Util.getInstance().MD5(imageUrl)
        val snapShot = diskLruCache.get(key)
        if (snapShot!=null){
            val inputStream = snapShot.getInputStream(0)
            return BitmapUtil.getInstance().getBitmap(inputStream,maxWidth,maxHeight,false)
        }
        return null
    }

    /**
     * 从缓存中删除指定的Bitmap
     * @param imageUrl 图片url
     */
    fun removeBitmapFromMemory(imageUrl: String) {
        val key = Md5Util.getInstance().MD5(imageUrl)
        diskLruCache.remove(key)
    }

    /**
     * 所有缓存数据的总字节数
     */
    fun getSize():Long{
        return diskLruCache.size()
    }

    /**
     * 同步数据,在Activity的onPause()方法中去调用一次flush()方法
     */
    fun flush(){
        diskLruCache.flush()
    }

    /**
     * 在Activity的onDestroy()方法中去调用close()方法
     */
    fun close(){
        diskLruCache.close()
    }

    /**
     * 将所有的缓存数据全部删除
     */
    fun delete(){
        diskLruCache.delete()
    }

    /**
     * 缓存目录
     */
    private fun directory(fileName: String): File {
        val path: String
        //判断SD卡是否可用
        if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState() || !Environment.isExternalStorageRemovable()) {
            path = application.externalCacheDir!!.absolutePath
        } else {
            path = application.cacheDir.absolutePath
        }
        val file = File(path, fileName)
        if (!file.exists()) {
            file.mkdirs()
        }
        return file
    }

    /**
     * app版本
     */
    @Throws(PackageManager.NameNotFoundException::class)
    private fun getAppVersion(): Int {
        val info = application.packageManager.getPackageInfo(application.packageName, 0)
        return info.versionCode
    }
}

如果你有使用okhttp,那么也可以使用okhttp的DiskLruCache

enum class OKDiskLruCacheUtil {
    INSTANCE;

    private lateinit var application: Application
    private lateinit var diskLruCache: DiskLruCache

    fun init(application: Application) {
        this.application = application
        try {
            diskLruCache = DiskLruCache.create(
                FileSystem.SYSTEM,
                directory("bitmap"),
                getAppVersion(),
                1,
                10 * 1024 * 1024
            )
        } catch (e: IOException) {
            e.printStackTrace()
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
    }

    /**
     * 把Bitmap对象加入到缓存中
     * @param imageUrl 图片url,如https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg
     * @param bitmap 加入缓存的图片
     */
    fun addBitmapToMemory(imageUrl: String){
        try {
            val key = Md5Util.getInstance().MD5(imageUrl)
            val editor = diskLruCache.edit(key)
            if (editor != null) {
                val outputStream= editor.newSink(0).buffer().outputStream()
                if (downloadUrlStream(imageUrl, outputStream)){
                    editor.commit()
                }else{
                    editor.abort()
                }
            }
            diskLruCache.flush()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    /**
     * 网络下载
     * @param imageUrl 图片url
     * @param outputStream diskLruCache的IO流
     * @return 是否下载成功
     */
    private fun downloadUrlStream(imageUrl: String, outputStream: OutputStream) :Boolean{
        val request = Request.Builder()
            .url(imageUrl)
            .build()

        try {
            val response = HttpUtil.getInstance().client.newCall(request).execute()
            if (response.isSuccessful) {
                if (response.body()!= null){
                    val bis = response.body()!!.byteStream().buffered()
                    val bos = outputStream.buffered()
                    try {
                        bis.copyTo(bos)
                        return true
                    }catch (e: IOException) {
                        e.printStackTrace()
                    }finally {
                        bis.close()
                        bos.close()
                    }
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return false
    }

    /**
     * 从缓存中得到Bitmap对象
     * @param imageUrl 图片url
     * @return Bitmap
     */
    fun getBitmapFromMemCache(imageUrl: String,maxWidth:Int,maxHeight:Int): Bitmap? {
        val key = Md5Util.getInstance().MD5(imageUrl)
        val snapShot = diskLruCache.get(key)
        if (snapShot!=null){
            val inputStream = snapShot.getSource(0).buffer().inputStream()
            return BitmapUtil.getInstance().getBitmap(inputStream,maxWidth,maxHeight,false)
        }
        return null
    }

    /**
     * 从缓存中删除指定的Bitmap
     * @param imageUrl 图片url
     */
    fun removeBitmapFromMemory(imageUrl: String) {
        val key = Md5Util.getInstance().MD5(imageUrl)
        diskLruCache.remove(key)
    }

    /**
     * 所有缓存数据的总字节数
     */
    fun getSize():Long{
        return diskLruCache.size()
    }

    /**
     * 同步数据,在Activity的onPause()方法中去调用一次flush()方法
     */
    fun flush(){
        diskLruCache.flush()
    }

    /**
     * 在Activity的onDestroy()方法中去调用close()方法
     */
    fun close(){
        diskLruCache.close()
    }

    /**
     * 将所有的缓存数据全部删除
     */
    fun delete(){
        diskLruCache.delete()
    }

    /**
     * 缓存目录
     */
    private fun directory(fileName: String): File {
        val path: String
        //判断SD卡是否可用
        if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState() || !Environment.isExternalStorageRemovable()) {
            path = application.externalCacheDir!!.absolutePath
        } else {
            path = application.cacheDir.absolutePath
        }
        val file = File(path, fileName)
        if (!file.exists()) {
            file.mkdirs()
        }
        return file
    }

    /**
     * app版本
     */
    @Throws(PackageManager.NameNotFoundException::class)
    private fun getAppVersion(): Int {
        val info = application.packageManager.getPackageInfo(application.packageName, 0)
        return info.versionCode
    }
}

三、三级缓存工具类

enum class KotlinCacheUtil {

    INSTANCE;

    private lateinit var application: Application

    /**
     * 初始化
     * @param application
     */
    fun init(application: Application) {
        this.application = application

        screenWidth()

        //内存缓存初始化
        LruCacheUtil.getInstance().init()
        //磁盘缓存初始化
        OKDiskLruCacheUtil.INSTANCE.init(application)
        //http初始化
        HttpUtil.getInstance().init()
    }

    /**
     * 三级缓存
     * @param imageUrl 图片url
     * @param imageView 显示控件
     * @param placeholderBitmap 占位图
     */
    private fun setImageBitmap(imageUrl: String, imageView: ImageView, placeholderBitmap: Bitmap) :Job{
        //设置占位图
        imageView.setImageBitmap(placeholderBitmap)

        return GlobalScope.launch {
            //1.先从内存读取
            var bitmap: Bitmap? = LruCacheUtil.getInstance().getBitmapFromMemCache(imageUrl)
            if (bitmap == null) {
                //2.如果没有则从磁盘读取
                bitmap = OKDiskLruCacheUtil.INSTANCE.getBitmapFromMemCache(imageUrl,width,height)
                if (bitmap == null) {
                    if (isNetworkConnected()) {
                        //3.从网络获取并加入磁盘缓存
                        OKDiskLruCacheUtil.INSTANCE.addBitmapToMemory(imageUrl)
                        //从磁盘缓存中获取bitmap
                        bitmap = OKDiskLruCacheUtil.INSTANCE.getBitmapFromMemCache(imageUrl,width,height)
                        if (bitmap == null) {
                            launch (Dispatchers.Main){
                                show("没有可用网络")
                            }
                            return@launch
                        }
                        //加入内存缓存
                        LruCacheUtil.getInstance().addBitmapToMemory(imageUrl, bitmap)
                    } else {
                        //4.如果没有网络提示
                        launch (Dispatchers.Main){
                            show("没有可用网络")
                        }
                        return@launch
                    }
                } else {
                    LruCacheUtil.getInstance().addBitmapToMemory(imageUrl, bitmap)
                }
            }
            launch (Dispatchers.Main){
                imageView.setImageBitmap(bitmap)
            }
        }
    }

    /**
     * 三级缓存
     * @param imageUrl 图片url
     * @param imageView 显示控件
     * @param resId 占位图的资源id
     */
    fun setImageBitmap(imageUrl: String, imageView: ImageView, resId: Int):Job {
        //设置占位图
        val bitmap = BitmapUtil.getInstance().getBitmapResources(application.resources,resId,width,height,false)
        return setImageBitmap(imageUrl,imageView,bitmap)
    }

    /**
     * 三级缓存
     * @param imageUrl 图片url
     * @param imageView 显示控件
     * @param file sd卡占位图
     */
    fun setImageBitmap(imageUrl: String, imageView: ImageView, file: File):Job {
        //设置占位图
        val bitmap = BitmapUtil.getInstance().getBitmapFile(file,width,height,false)
        return setImageBitmap(imageUrl,imageView,bitmap)
    }

    /**
     * 三级缓存
     * @param imageUrl 图片url
     * @param imageView 显示控件
     * @param path      占位图文件路径
     * @param fileName  占位图文件名字
     */
    fun setImageBitmap(imageUrl: String, imageView: ImageView, path:String , fileName:String):Job {
        //设置占位图
        val bitmap = BitmapUtil.getInstance().getBitmapFile(path,fileName,width,height,false)
        return setImageBitmap(imageUrl,imageView,bitmap)
    }

    /**
     * 清除所有缓存
     */
    private fun delete() {
        LruCacheUtil.getInstance().evictAll()
        OKDiskLruCacheUtil.INSTANCE.delete()
    }

    /**
    * 检测是否有网络连接
    * @return 返回false无可用网络
    */
    private fun isNetworkConnected(): Boolean {
        val mConnectivityManager = application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val mNetworkInfo = mConnectivityManager.activeNetworkInfo
        if (mNetworkInfo != null) {
            return mNetworkInfo.isConnected
        }
        return false
    }

    private fun show(msg: String) {
        Toast.makeText(application, msg, Toast.LENGTH_LONG).show()
    }

    private var width = 0
    private var height = 0
    /**
     * 获取屏幕宽高
     */
    private fun screenWidth() {
        val wm = application.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        val display = wm.defaultDisplay
        val size = Point()
        display.getSize(size)
        width = size.x
        height = size.y
    }
}

四、网络工具类

一个简单的小工具类,为懒癌患者准备

public class HttpUtil {

    private static volatile HttpUtil instance;
    private HttpUtil() {}
    public static HttpUtil getInstance() {
        if (instance == null) {
            synchronized (HttpUtil.class) {
                if (instance == null) {
                    instance = new HttpUtil();
                }
            }
        }
        return instance;
    }

    private OkHttpClient client;
    public void init(){
        client  = new OkHttpClient.Builder()
                //设置连接的连接超时的时间
                .connectTimeout(10,TimeUnit.SECONDS)
                //设置连接的读取超时时间
                .readTimeout(30, TimeUnit.SECONDS)
                //设置写入超时时间
                .writeTimeout(10, TimeUnit.SECONDS)
                .build();
    }

    public OkHttpClient getClient(){
        return client;
    }

}

五、bitmap工具类

https://blog.csdn.net/a896159476/article/details/92996925

完整代码

https://github.com/a896159476/KotlinTest

你可能感兴趣的:(安卓基础)