天天看點

一行代碼搞定三級緩存

Android的三級緩存,其中主要的就是記憶體緩存和硬碟緩存,分别是LruCache和DisLruCache。

LruCache是Android 3.1所提供的一個緩存類,是以在Android中可以直接使用LruCache實作記憶體緩存。而DisLruCache目前在Android 還不是Android SDK的一部分,但Android官方文檔推薦使用該算法來實作硬碟緩存。

簡單使用

var listJob = ArrayList<Job>()
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卡需要動态申請權限,自行百度

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
           

一、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<String, Bitmap> lruCache;

    /**
     * 初始化
     */
    public void init() {
        long maxMemory = Runtime.getRuntime().maxMemory();
        int cacheSize = (int) (maxMemory / 8);
        lruCache = new LruCache<String, Bitmap>(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("沒有可用網絡")
                            }
                            [email protected]
                        }
                        //加入記憶體緩存
                        LruCacheUtil.getInstance().addBitmapToMemory(imageUrl, bitmap)
                    } else {
                        //4.如果沒有網絡提示
                        launch (Dispatchers.Main){
                            show("沒有可用網絡")
                        }
                        [email protected]
                    }
                } 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

繼續閱讀