使用LruCache和DiskLruCache手写一个ImageLoader

Posted 飘杨......

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用LruCache和DiskLruCache手写一个ImageLoader相关的知识,希望对你有一定的参考价值。

一、概述

  在分析OkHttp3的缓存机制之前先手写一个实现了三级缓存的ImageLoader来整体感受一下LruCache和DiskLruCache的用法。本例实现了三级缓存,利用LruCache实现内存缓存,利用DiskLruCache实现磁盘缓存。整体的流程是:当用户请求一张图时,首先检查内存中是否有缓存图片,如果有就直接返回,如果没有就检查磁盘中是否有,有就返回,没有就启用网络下载图片,然后把图片缓存在磁盘缓存和内存缓存中。下面看看具体的实现步骤。

二、源程序介绍

  1.ImageLoader.java:是一个单例类,是加载图片的入口.

  2.CacheManager.java用来管理LruCacheManger和DiskLruCacheManager

  3.LruCacheManager.java是用来管理内存缓存的。

  4.DiskLruCacheManager是用来管理磁盘缓存的

  5.DownloadFileUtil.java是用来下载文件的,本例指的是图片

  6.MainThreadHandler.java是创建了一个运行在主线程中的Handler

  7.Util.java工具类

  8.MsgConfig.java配置类

  9.LoaderResult.java存储view、bitmap和uri的工具类

  10.ImageResizer.java用来缩放图片用的

  整体类图结构

 

 ImageLoader.java中就放了一个线程池(自定义线程池)外加displayView显示方法

public class ImageLoader {
    private ImageLoader(Context context) {
        mainThreadHandler = new MainThreadHandler();
        cacheManager = new CacheManager(context, THREAD_POOL_EXECUTOR, mainThreadHandler);
    }

    private static final String TAG = "ImageLoader";
    public static final int MESSAGE_POST_RESULT = 1;
    //核心Cpu数量
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;//核心线程数量
    //线程池的最大容量
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final long KEEP_ALIZE = 10l;


    private static final ThreadFactory threadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
        }
    };
    //创建一个线程池
    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIZE, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), threadFactory);
    private Context context;
    private CacheManager cacheManager;
    private MainThreadHandler mainThreadHandler;
    private static ImageLoader imageLoader = null;
    public static ImageLoader getDefault(Context context) {
        synchronized (ImageLoader.class){
            if(imageLoader==null){
                imageLoader = new ImageLoader(context);
            }
        }
        return imageLoader;
    }



    /**
     * 异步加载一张图片并显示在view上
     *
     * @param url
     * @param view
     */
    public void displayView(String url, View view) {

        cacheManager.bindBitmap(url, view);
    }

    /**
     * 根据url获取一个bitmap
     *
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public Bitmap getBitmap(String url, int reqWidth, int reqHeight) {
        return cacheManager.loadBitmap(url, reqWidth, reqHeight);
    }

    /**
     * 异步加载指定图片的大小加载图片
     *
     * @param url
     * @param view
     * @param reqWidth
     * @param reqHeight
     */
    public void displayView(String url, View view, int reqWidth, int reqHeight) {
        cacheManager.bindBitmap(url, view, reqWidth, reqHeight);
    }


}

 CacheManager.java用来管理磁盘缓存 和内存缓存,并实现三级缓存的判断

package com.yw.library;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.os.Looper;
import android.os.StatFs;
import android.util.LruCache;
import android.view.View;
import android.widget.ImageView;

import com.jakewharton.disklrucache.DiskLruCache;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 缓存管理类
 * create by yangwei
 * on 2020-03-01 22:13
 */
public class CacheManager {
    private ImageResizer imageResizer = new ImageResizer();
    private Executor THREAD_POOL_EXECUTOR;
    //磁盘缓存的最大容量
    public static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
    public static final int DISK_CACHE_INDEX = 0;
    private LruCacheManager lruCacheManager;
    private DiskLruCacheManager diskLruCacheManager;
    private MainThreadHandler mainThreadHandler;
    private Context context;
    public CacheManager(Context context,Executor THREAD_POOL_EXECUTOR,MainThreadHandler mainThreadHandler) {
        this.THREAD_POOL_EXECUTOR = THREAD_POOL_EXECUTOR;
        this.mainThreadHandler = mainThreadHandler;
        this.context = context;
        lruCacheManager = new LruCacheManager();
        diskLruCacheManager = new DiskLruCacheManager(context,imageResizer,lruCacheManager);
    }


    public void bindBitmap(final String uri, final View view) {
        bindBitmap(uri, view, 0, 0);
    }

    public void bindBitmap(final String uri, final View view, final int reqWidth, final int reqHeight) {
        view.setTag(MsgConfig.TAG_KEY_URI, uri);
        Bitmap bitmap = loadBitmapFromMemoryCache(uri);
        if (bitmap != null) {
            if (view instanceof ImageView) {
                ((ImageView) view).setImageBitmap(bitmap);
            } else {
                view.setBackground(new BitmapDrawable(bitmap));
            }
            return;
        }
        //利用线程池在后台加载图片,加载成功后进入主线程更新UI
        THREAD_POOL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
                LoaderResult loaderResult = new LoaderResult(view, uri, bitmap);
                //向主线程中发送loaderresult
                mainThreadHandler.obtainMessage(MsgConfig.MESSAGE_POST_RESULT, loaderResult).sendToTarget();
            }
        });
    }

    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
        Bitmap bitmap = loadBitmapFromMemoryCache(uri);
        if (bitmap != null) {
            return bitmap;
        }
        bitmap = diskLruCacheManager.loadBitmapFromDiskCache(uri, reqHeight, reqHeight);
        if (bitmap != null) {
            return bitmap;
        }
        bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
        if (bitmap != null) {
            return bitmap;
        }
        if (bitmap == null && !diskLruCacheManager.mIsDiskLruCacheCreated) {
            bitmap = DownloadFileUtil.get().downloadBitmapFromUrl(uri);
        }
        return bitmap;
    }

    private Bitmap loadBitmapFromMemoryCache(String url) {
        final String key = Util.hashKeyFromUrl(url);
        Bitmap bitmap = lruCacheManager.getBitmapFromMemoryCache(key);
        return bitmap;
    }

    private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) {
        diskLruCacheManager.addBitmapToDiskCache(url);
        return diskLruCacheManager.loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

    public static class Builder {
        public Builder(){}

    }

}

  LruCacheManager.java用来管理LruCache

package com.yw.library;

import android.graphics.Bitmap;
import android.util.LruCache;

/**
 * create by yangwei
 * on 2020-03-01 22:30
 */
public class LruCacheManager {
    private LruCache<String, Bitmap> mMemoryCache = null;

    public LruCacheManager() {
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //返回bitmap的大小,单位是kb
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    /**
     * 相内存缓存中添加一个Bitmap
     *
     * @param key
     * @param bitmap
     */
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemoryCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    /**
     * 从内存缓存中取出一个Bitmap
     *
     * @param key
     * @return
     */
    public Bitmap getBitmapFromMemoryCache(String key) {
        return mMemoryCache.get(key);
    }
}

  DissLruCacheManager用来管理DiskLruCache

package com.yw.library;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Looper;
import android.os.StatFs;

import com.jakewharton.disklrucache.DiskLruCache;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.OutputStream;

/**
 * 磁盘缓存管理
 * create by yangwei
 * on 2020-03-01 22:30
 */
public class DiskLruCacheManager {
    private DiskLruCache mDiskLruCache = null;
    private Context context;
    public boolean mIsDiskLruCacheCreated = false;
    private ImageResizer imageResizer;
    private LruCacheManager lruCacheManager;

    /**
     * 初始化参数
     *
     * @param context
     * @param imageResizer
     * @param lruCacheManager
     */
    public DiskLruCacheManager(Context context, ImageResizer imageResizer, LruCacheManager lruCacheManager) {
        this.context = context;
        this.imageResizer = imageResizer;
        this.lruCacheManager = lruCacheManager;
        //创建磁盘缓存路径
        File diskCacheDir = FileUtil.createDiskCacheDir(context, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }
        if (getUsableSpace(diskCacheDir) > CacheManager.DISK_CACHE_SIZE) {
            try {
                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, CacheManager.DISK_CACHE_SIZE);
                mIsDiskLruCacheCreated = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 添加Bitmap到磁盘
     *
     * @param url
     */
    public void addBitmapToDiskCache(String url) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network form Ui Thread");
        }
        try {
            String key = Util.hashKeyFromUrl(url);
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);
            if (editor != null) {
                OutputStream outputStream = editor.newOutputStream(CacheManager.DISK_CACHE_INDEX);
                if (DownloadFileUtil.get().downloadUrlToStream(url, outputStream)) {
                    editor.commit();
                } else {
                    editor.abort();
                }
                mDiskLruCache.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 从磁盘中加载图片
     *
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not load bitmap from UI Thread");
        }
        if (mDiskLruCache == null) {
            return null;
        }
        Bitmap bitmap = null;
        try {
            String key = Util.hashKeyFromUrl(url);
            DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
            if (snapshot != null) {
                FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(CacheManager.DISK_CACHE_INDEX);
                FileDescriptor fileDescriptor = fileInputStream.getFD();
                bitmap = imageResizer.decodeBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight);
                try {
                    if (bitmap.getHeight() > 0) {
                        if (bitmap != null) {
                            lruCacheManager.addBitmapToMemoryCache(key, bitmap);
                        }
                    }
                } catch (Exception e) {
                    return null;
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    public long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return path.getUsableSpace();
        }
        final StatFs statFs = new StatFs(path.getPath());
        return statFs.getBlockSizeLong() * statFs.getAvailableBlocksLong();
    }

}

 

总结:整体逻辑的构建基本上都在CacheManager中,逻辑还是比较清楚的。

 

 

ps:完整代码在github,有需要的可以去下载。下载路径如下:

去github下载完整源码

 

 

 

以上是关于使用LruCache和DiskLruCache手写一个ImageLoader的主要内容,如果未能解决你的问题,请参考以下文章

Android 缓存LruCache和DiskLruCache

综合使用LruCache和DiskLruCache 缓存图片

Android照片墙完整版,完美结合LruCache和DiskLruCache

Android-DiskLruCache

Android内存优化之 LruCache与DiskLruCache

Android 加载图片优化 LruCache DiskLruCache