随笔 - 632  文章 - 17  评论 - 54  阅读 - 93万

使用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显示方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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用来管理磁盘缓存 和内存缓存,并实现三级缓存的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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下载完整源码

 

 

 

posted on   飘杨......  阅读(419)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示