使用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,有需要的可以去下载。下载路径如下:

 

原文地址:https://www.cnblogs.com/tony-yang-flutter/p/12394761.html

时间: 2024-11-08 00:51:24

使用LruCache和DiskLruCache手写一个ImageLoader的相关文章

放弃antd table,基于React手写一个虚拟滚动的表格

缘起 标题有点夸张,并不是完全放弃antd-table,毕竟在react的生态圈里,对国人来说,比较好用的PC端组件库,也就antd了.即便经历了2018年圣诞彩蛋事件,antd的使用者也不仅不减,反而有所上升. 客观地说,antd是开源的,UI设计得比较美观(甩出其他组件库一条街),而且是蚂蚁金服的体验技术部(一堆p7,p8,p9,基本都是大牛级的)在持续地开发维护,质量可以信任. 不过,antd虽好,但一些组件在某一些场景下,是很不适用的.例如,以表格形式无限滚动地展示大量数据(1w+)时,

利用SpringBoot+Logback手写一个简单的链路追踪

目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简单的链路追踪,下面详细介绍下. 一.实现原理 Spring Boot默认使用LogBack日志系统,并且已经引入了相关的jar包,所以我们无需任何配置便可以使用LogBack打印日志. MDC(Mapped Diagnostic Context,映射调试上下文)是log4j和logback提供的一种

手写一个模块化的 TCP 服务端客户端

前面的博客 基于 socket 手写一个 TCP 服务端及客户端 写过一个简单的 TCP 服务端客户端,没有对代码结构进行任何设计,仅仅是实现了相关功能,用于加深对 socket 编程的认识. 这次我们对整个代码结构进行一下优化,使其模块化,易扩展,成为一个简单意义上的“框架”. 对于 Socket 编程这类所需知识偏底层的情况(OS 协议栈的运作机制,TCP 协议的理解,多线程的理解,BIO/NIO 的理解,阻塞函数的运作原理甚至是更底层处理器的中断.网卡等外设与内核的交互.核心态与内核态的切

Spring系列之手写一个SpringMVC

目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 Spring系列之AOP的原理及手动实现 Spring系列之手写注解与配置文件的解析 引言 在前面的几个章节中我们已经简单的完成了一个简易版的spring,已经包括容器,依赖注入,AOP和配置文件解析等功能.这一节我们来实现一个自己的springMvc. 关于MVC/SpringMVC springMvc是一个基于mvc模式的web框架,SpringMVC框架是一种提供了MVC(模型 - 视图 - 控制器)架

css手写一个表头固定

Bootstrap,layui等前端框架里面都对表头固定,表格滚动有实现,偏偏刚入职的公司选择了手动渲染表格,后期又觉得表格数据拉太长想要做表头固定.为了避免对代码改动太大,所以决定手写表头固定 主要遇到的个问题就是固定以后数据表格与表头的对齐问题,也看了很多我文章试下来都不怎么成功,只好自己一点点试 表头固定的一般思路是布两个table,一个放表头,一个放表格体,然后将表格体加上高度height以及overflow-y <div class="content"> <

手写一个IOC容器

链接:https://pan.baidu.com/s/1MhKJYamBY1ejjjhz3BKoWQ 提取码:e8on 明白什么是IOC容器: IOC(Inversion of Control,控制反转).这是spring的核心,贯穿始终.所谓IOC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系. 传统的java代码中,我们需要使用哪个对象,就new一个对象,很正常对吧? 然而,这时出现了一个新思想:IOC(控制反转) 由它创建和管理所有的对象,我们需要的时

手写一个词法分析器

前言 最近大部分时间都在撸 Python,其中也会涉及到将数据库表转换为 Python 中 ORM 框架的 Model,但我们并没有找到一个合适的工具来做这个意义不大的"体力活",所以每次新建表后大家都是根据自己的表结构手写一遍 Model. 一两张表还好,一旦 10 几张表都要写一遍时那痛苦只有自己知道:这时程序员的 slogan 再次印证:一切毫无意义的体力劳动终将被计算机取代. intellij plugin 既然没有现成的工具那就自己写一个吧,演示效果如下: 考虑到我们主要是用

爬虫入门 手写一个Java爬虫

本文内容 涞源于  罗刚 老师的 书籍 << 自己动手写网络爬虫一书 >> ; 本文将介绍 1: 网络爬虫的是做什么的?  2: 手动写一个简单的网络爬虫; 1: 网络爬虫是做什么的?  他的主要工作就是 跟据指定的url地址 去发送请求,获得响应, 然后解析响应 , 一方面从响应中查找出想要查找的数据,另一方面从响应中解析出新的URL路径, 然后继续访问,继续解析;继续查找需要的数据和继续解析出新的URL路径  . 这就是网络爬虫主要干的工作.  下面是流程图: 通过上面的流程图

记一道面试题:手写一个内存泄漏的代码,如何修正

前几天面试的时候被问到手写一个内存泄漏的代码,并且如何修正,当时有点蒙,后来面试官写了三行代码,如下 1 Object obj1=new Object(); 2 Object obj2=new Object(); 3 obj1=obj2; 试分析是否出现内存泄漏,为什么, 首先,先了解一下内存泄漏是什么?百度百科给了一个答案 简单的说就是我们已经不需要的对象,它没有被清理,依旧存在堆内存中.但是项目一旦运行又不会时长终止,那么这个对象就会一直存在,占用空间也会进行累计. 上面的代码是存在内存泄漏