图片三级缓存

目录


Android系统的内存区域

Google Glide与Facebook Fresco的缓存比较

Glide

Fresco

对比

一. 四大图片缓存基本信息

二、基本概念

三、共同优点

四、ImageLoader 设计及优点

五、Picasso 设计及优点

六、Glide 设计及优点

七、汇总

三级缓存的原理与实现

1、原理

2、实现

3、调用


Android系统的内存区域

首先,我们需要了解Android系统提供的不同内存区域。

1)Java heap: 该区域被设备开发商对每个应用程序设定了严格的空间限制。所有使用Java语言的new操作符创建的对象都保存在这里。此处的内存会被Java的垃圾回收机制处理,即当App不再使用该区域的某块内存时,系统会自动回收它。

不幸的是,垃圾回收过程本身也是一个问题。当回收内存较多时,Android系统会完全停止应用程序以便执行回收。这也是应用程序冻屏或卡顿的最常见的应用。这些很影响用户的体验,他们此时可能正在滑动或点击按钮,却只能等待应用程序做出反应。

2)Native heap: 使用C++语音的new操作符创建的对象放在这个区域。在该区域App只受设备的物理内存的限制。并且没有垃圾回收机制,不用担心应用程序卡顿的问题。但是,C++应用程序需要负责释放分配的所有内存,否则会引起内存泄漏,最终导致应用程序崩溃。

3)ashmem:也叫匿名共享内存。它的行为与native heap很像,但是多了额外的系统调用。Android系统可以“unpin"该内存而不用释放它。只是一种懒惰模式的释放,内存只有在系统趋势需要更多的内存时才真正被释放。当Android系统"pin"该内存后,如果该内存没有被释放过的话,旧的数据仍然存在。

https://blog.csdn.net/lufqnuli/article/details/51517999

Google Glide与Facebook Fresco的缓存比较

Glide

特点:

  • 可以传入activity、fragment等,图片的加载会和activity、fragment生命周期一致(比如onPause暂停、onResume重新加载,onDestory销毁)
  • 默认Bitmap格式RGB_565(每像素2B,去透明度)
  • Glide加载的大小与imageview一致
  • 可加载gif、WebP
  • 可配置图片动画
  • 体积500 KB

缓存策略:

  • MemeryCache(LruCache最近最少使用算法)
  • WeakReference(activeResources弱引用,每次GC会被回收)
  • DiskCache(磁盘缓存)

加载过程:

  • 正在使用中的图片使用弱引用activeResources来进行缓存,不再使用中的图片使用LruCache来进行缓存;
  • activeResources保证正在使用的图片不被Lru算法回收。
  • 如果要使用图片,首先从LruCache取出图片,然后存到activeResources,图片释放的时候,再把图片存到LruCache。这样在使用LRU算法的时候,正在使用的图片在弱引用里,就不会被回收。
  • 硬盘缓存,默认情况下在硬盘缓存的就是转换过后的图片。

缓存处理关键类:LruResourceCache、DiskCache
 

Fresco

特点:

  • SimpleDraweeView
  • 图像的渐进式呈现
  • 体积太大2~3M
  • 也可加载gif、webP
  • 5.0以下系统,把bitmap保存到ashmen(匿名共享内存机制),不会启动gc,使得界面不会因为gc而卡死。

缓存策略:

Fresco使用三级缓存,已解码内存缓存;未解码内存缓存;磁盘缓存

  1. 第一级缓存就是保存Bitmap,Bitmap缓存存储Bitmap对象,这些Bitmap对象可以立刻用来显示或者用于后处理。在5.0以下系统,Bitmap缓存位于ashmem(匿名共享内存),这样Bitmap对象的创建和释放将不会引发GC,更少的GC会使你的APP运行得更加流畅。5.0及其以上系统,相比之下,内存管理有了很大改进,所以Bitmap缓存直接位于Java的heap上。当应用在后台运行时,该内存会被清空。
  2. 第二级缓存保存在内存,是未解码图片的内存缓存;这个缓存存储的是原始压缩格式的图片(如jpg格式)。从该缓存取到的图片在使用之前,需要先进行解码。如果有调整大小,旋转,或者WebP编码转换工作需要完成,这些工作会在解码之前进行。APP在后台时,这个缓存同样会被清空。
  3. 第三级缓存就是文件缓存,保存在本地文件,和未解码的内存缓存相似,文件缓存存储的是未解码的原始压缩格式的图片,在使用之前同样需要经过解码等处理。

在5.0以下,GC将会显著地引发界面卡顿。Fresco将图片放到一个特别的内存区域。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。

加载图片的流程:

查找Bitmap缓存中是否存在,存在则直接返回Bitmap直接使用,不存在则查找未解码图片的缓存,如果存在则进行Decode成Bitmap然后直接使用并加入Bitmap缓存中,如果未解码图片缓存中查找不到,则进行硬盘缓存的检查,如有,则进行IO、转化、解码等一系列操作,最后成Bitmap供我们直接使用,并把未解码(Encode)的图片加入未解码图片缓存,把Bitmap加入Bitmap缓存中,如硬盘缓存中没有,则进行Network操作下载图片,然后加入到各个缓存中。

源码缓存处理位置:

com.facebook.imagepipeline.core.ImagePipelineFactory初始化com.facebook.imagepipeline.core.ImagePipeline.java
ImagePipeline负责图片的获取和管理。图片可以来自远程服务器,本地文件,或者Content Provider,本地资源。压缩后的文件缓存在本地存储中,Bitmap数据缓存在内存中。 
在5.0系统以下,Image Pipeline 使用 pinned purgeables 将Bitmap数据避开Java堆内存,存在ashmem中。这要求图片不使用时,要显式地释放内存。 
SimpleDraweeView自动处理了这个释放过程,所以没有特殊情况,尽量使用SimpleDraweeView,在特殊的场合,如果有需要,也可以直接控制Image Pipeline。
我们可以通过ImagePipeline 判断bitmap是否被缓存,
ImagePipeline imagePipeline = Fresco.getImagePipeline();
imagePipeline.isInBitmapMemoryCache(Uri.parse(""));
imagePipeline.isInDiskCache(Uri.parse("xxx"));
删除指定缓存
Uri uri = Uri.parse("xxx");
imagePipeline.evictFromCache(uri);
imagePipeline.evictFromDiskCache(uri);

https://blog.csdn.net/caidai1989/article/details/73742416

Fresco中的MVC模式

Fresco框架整体是一个MVC模式

DraweeView——View
DraweeController——Control
DraweeHierarchy——Model

它们之间的关系大致如下:

  • DraweeHierarchy意为视图的层次结构,用来存储和描述图片的信息,同时也封装了一些图片的显示和视图层级的方法。
  • DraweeView用来显示顶层视图(getTopLevelDrawable())。DraweeController控制加载图片的配置、顶层显示哪个视图以及控制事件的分发。

【注】DraweeView目前版本时继承于ImageView,但这并不意味着我们可以随意的使用ImageView相关的方法(如:setScaleType等),官方并不建议我们使用,因为后期DraweeView将继承于View,所以最好只使用DraweeView控件内置的方法。

APP缓存和内存缓存不一样,APP在后台时,内容是不会被清空的。即使关机也不会。用户可以随时用系统的设置菜单中进行清空缓存操作。 在Fresco介绍:Android的一个新图片库中,我们已经知道Fresco的缓存是由Producer/Consumer的框架来实现的。 图片获取是由各级Producer实现的,而将获取到的图片添加到缓存中是由各级Cusumer来实现的。 

  • SD的路径:/storage/emulated/0 
  • SD卡的挂载状态:mounted-->正常加载的状态值
  • 有SD卡的情况:缓存路径:/storage/emulated/0/Android/data/com.zzy/cache
  • 无SD卡的情况:缓存路径:/data/data/com.zzy/cache
  • 有SD卡的情况 :下载路径:/storage/emulated/0/Android/data/com.zzy/files/test
  • 无SD卡的情况:下载路径:getFilesDir: /data/data/com.zzy/files

现在我们已经能够清楚知道我们清楚数据和清除缓存的具体位置了;当我们卸载应用的时候,系统会把data/应用包名  下面的files目录和cahe目录全部自动删除掉。但是如果我们把文件下载在SD卡的根路径,系统不会帮我们回收,需要用户手动删除,因此,优秀的程序员都应考虑良好的用户体验,即使由于某种原因卸载了我们的应用,我们还是应该在对应用数据位置进行最优的存储:临时数据存放在cache目录下,持久化的数据存储在files。

清除数据、清除缓存的区别

  • 清除数据主要是清除用户配置,比如SharedPreferences、数据库等等,这些数据都是在程序运行过程中保存的用户配置信息,清除数据后,下次进入程序就和第一次进入程序时一样
  • 缓存是程序运行时的临时存储空间,它可以存放从网络下载的临时图片,从用户的角度出发清除缓存对用户并没有太大的影响,但是清除缓存后用户再次使用该APP时,由于本地缓存已经被清理,所有的数据需要重新从网络上获取。为了在清除缓存的时候能够正常清除与应用相关的缓存,请将缓存文件存放在getCacheDir()或者 getExternalCacheDir()路径下。

https://blog.csdn.net/yangqq2013/article/details/50523214

对比

从总体设计和原理上对几个图片缓存进行对比,没用到他们的朋友也可以了解他们在某些特性上的实现。关于选择开源项目的好处及如何选择开源项目可见:开源项目使用及选型

一. 四大图片缓存基本信息


Universal ImageLoader 是很早开源的图片缓存,在早期被很多应用使用。

Picasso 是 Square 开源的项目,且他的主导者是 JakeWharton,所以广为人知。

Glide 是 Google 员工的开源项目,被一些 Google App 使用,在去年的 Google I/O 上被推荐,不过目前国内资料不多。

Fresco 是 Facebook 在今年上半年开源的图片缓存,主要特点包括:

  • 两个内存缓存加上 Native 缓存构成了三级缓存
  • 支持流式,可以类似网页上模糊渐进式显示图片
  • 对多帧动画图片支持更好,如 Gif、WebP

更多图片缓存库可见:Android 图片缓存库

二、基本概念

在正式对比前,先了解几个图片缓存通用的概念:
(1) RequestManager:请求生成和管理模块

(2) Engine:引擎部分,负责创建任务(获取数据),并调度执行

(3) GetDataInterface:数据获取接口,负责从各个数据源获取数据。
比如 MemoryCache 从内存缓存获取数据、DiskCache 从本地缓存获取数据,下载器从网络获取数据等。

(4) Displayer:资源(图片)显示器,用于显示或操作资源。
比如 ImageView,这几个图片缓存都不仅仅支持 ImageView,同时支持其他 View 以及虚拟的 Displayer 概念。

(5) Processor 资源(图片)处理器
负责处理资源,比如旋转、压缩、截取等。

以上概念的称呼在不同图片缓存中可能不同,比如 Displayer 在 ImageLoader 中叫做 ImageAware,在 Picasso 和 Glide 中叫做 Target。

三、共同优点

1. 使用简单
都可以通过一句代码可实现图片获取和显示。

2. 可配置度高,自适应程度高
图片缓存的下载器(重试机制)、解码器、显示器、处理器、内存缓存、本地缓存、线程池、缓存算法等大都可轻松配置。

自适应程度高,根据系统性能初始化缓存配置、系统信息变更后动态调整策略。
比如根据 CPU 核数确定最大并发数,根据可用内存确定内存缓存大小,网络状态变化时调整最大并发数等。

3. 多级缓存
都至少有两级缓存、提高图片加载速度。

4. 支持多种数据源
支持多种数据源,网络、本地、资源、Assets 等。

5. 支持多种 Displayer
不仅仅支持 ImageView,同时支持其他 View 以及虚拟的 Displayer 概念。

其他小的共同点包括支持动画、支持 transform 处理、获取 EXIF 信息等。

四、ImageLoader 设计及优点

1. 总体设计及流程

上面是 ImageLoader 的总体设计图。整个库分为 ImageLoaderEngine,Cache 及 ImageDownloader,ImageDecoder,BitmapDisplayer,BitmapProcessor 五大模块,其中 Cache 分为 MemoryCache 和 DiskCache 两部分。

简单的讲就是 ImageLoader 收到加载及显示图片的任务,并将它交给 ImageLoaderEngine,ImageLoaderEngine 分发任务到具体线程池去执行,任务通过 Cache 及 ImageDownloader 获取图片,中间可能经过 BitmapProcessor 和 ImageDecoder 处理,最终转换为Bitmap 交给 BitmapDisplayer 在 ImageAware 中显示。

2. ImageLoader 优点

(1) 支持下载进度监听

(2) 可以在 View 滚动中暂停图片加载
通过 PauseOnScrollListener 接口可以在 View 滚动中暂停图片加载。

(3) 默认实现多种内存缓存算法 这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。

(4) 支持本地缓存文件名规则定义

五、Picasso 设计及优点

1. 总体设计及流程

上面是 Picasso 的总体设计图。整个库分为 Dispatcher,RequestHandler 及 Downloader,PicassoDrawable 等模块。

Dispatcher 负责分发和处理 Action,包括提交、暂停、继续、取消、网络状态变化、重试等等。

简单的讲就是 Picasso 收到加载及显示图片的任务,创建 Request 并将它交给 Dispatcher,Dispatcher 分发任务到具体 RequestHandler,任务通过 MemoryCache 及 Handler(数据获取接口) 获取图片,图片获取成功后通过 PicassoDrawable 显示到 Target 中。

需要注意的是上面 Data 的 File system 部分,Picasso 没有自定义本地缓存的接口,默认使用 http 的本地缓存,API 9 以上使用 okhttp,以下使用 Urlconnection,所以如果需要自定义本地缓存就需要重定义 Downloader。

2. Picasso 优点

(1) 自带统计监控功能
支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。

(2) 支持优先级处理
每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。

(3) 支持延迟到图片尺寸计算完成加载

(4) 支持飞行模式、并发线程数根据网络类型而变
手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4, 4g 为 3,3g 为 2。
这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。

(5) “无”本地缓存
无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。(Fresco应该是两个内存缓存,一个external 缓存组成的三级缓存?native缓存应该也属于内存缓存吧。)

六、Glide 设计及优点

1. 总体设计及流程

上面是 Glide 的总体设计图。整个库分为 RequestManager(请求管理器),Engine(数据获取引擎)、 Fetcher(数据获取器)、MemoryCache(内存缓存)、DiskLRUCache、Transformation(图片处理)、Encoder(本地缓存存储)、Registry(图片类型及解析器配置)、Target(目标) 等模块。

简单的讲就是 Glide 收到加载及显示资源的任务,创建 Request 并将它交给RequestManager,Request 启动 Engine 去数据源获取资源(通过 Fetcher ),获取到后 Transformation 处理后交给 Target。

Glide 依赖于 DiskLRUCache、GifDecoder 等开源库去完成本地缓存和 Gif 图片解码工作。

2. Glide 优点

(1) 图片缓存->媒体缓存
Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。

(2) 支持优先级处理

(3) 与 Activity/Fragment 生命周期一致,支持 trimMemory
Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用。

(4) 支持 okhttp、Volley
Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。

(5) 内存友好
① Glide 的内存缓存有个 active 的设计
从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。

② 内存缓存更小图片
Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小

③ 与 Activity/Fragment 生命周期一致,支持 trimMemory

④ 图片默认使用默认 RGB_565 而不是 ARGB_888
虽然清晰度差些,但图片更小,也可配置到 ARGB_888。

其他:Glide 可以通过 signature 或不使用本地缓存支持 url 过期

七、汇总


三者总体上来说,ImageLoader 的功能以及代理容易理解长度都一般。

Picasso 代码虽然只在一个包下,没有严格的包区分,但代码简单、逻辑清晰,一两个小时就能叫深入的了解完。

Glide 功能强大,但代码量大、流转复杂。在较深掌握的情况下才推荐使用,免得出了问题难以下手解决。

http://www.trinea.cn/android/android-image-cache-compare/

三级缓存的原理与实现

  在Android开发中,如果图片过多,而我们又没有对图片进行有效的缓存,就很容易导致OOM(Out Of Memory)错误。因此,图片的缓存是非常重要的,尤其是对图片非常多的应用。现在很多框架都做了很好的图片缓存处理,如【Fresco】【Glide】等。

  本帖主要介绍以下Android中图片的三级缓存机制的原理及其应用。本帖中的代码都是使用Android原生的代码编写的。

1、原理

  Android图片三级缓存的原理如下图所示:

  可见,Android中图片的三级缓存主要是强引用、软引用和文件系统。

  Android原生为我们提供了一个LruCache,其中维护着一个LinkedHashMap。LruCache可以用来存储各种类型的数据,但最常见的是存储图片(Bitmap)。LruCache创建LruCache时,我们需要设置它的大小,一般是系统最大存储空间的八分之一。LruCache的机制是存储最近、最后使用的图片,如果LruCache中的图片大小超过了其默认大小,则会将最老、最远使用的图片移除出去。

  当图片被LruCache移除的时候,我们需要手动将这张图片添加到软引用(SoftReference)中。我们需要在项目中维护一个由SoftReference组成的集合,其中存储被LruCache移除出来的图片。软引用的一个好处是当系统空间紧张的时候,软引用可以随时销毁,因此软引用是不会影响系统运行的,换句话说,如果系统因为某个原因OOM了,那么这个原因肯定不是软引用引起的。

  下面叙述一下三级缓存的流程:

  当我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,如果LruCache中没有,则去SoftReference中寻找,如果SoftReference中有,则从SoftReference中取出图片使用,同时将图片重新放回到LruCache中,如果SoftReference中也没有图片,则去文件系统中寻找,如果有则取出来使用,同时将图片添加到LruCache中,如果没有,则连接网络从网上下载图片。图片下载完成后,将图片保存到文件系统中,然后放到LruCache中。

2、实现

(1)网络访问工具类HttpUtil:

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * 访问Http的工具类
 */
public class HttpUtil {
    private static HttpUtil instance;

    private HttpUtil() {
    }

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

    /**
     * 通过path(URL)访问网络获取返回的字节数组
     */
    public byte[] getByteArrayFromWeb(String path) {
        byte[] b = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            URL url = new URL(path);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setDoInput(true);
            connection.setConnectTimeout(5000);
            connection.connect();
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                baos = new ByteArrayOutputStream();
                is = connection.getInputStream();
                byte[] tmp = new byte[1024];
                int length = 0;
                while ((length = is.read(tmp)) != -1) {
                    baos.write(tmp, 0, length);
                }
            }
            b = baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (baos != null) {
                    baos.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return b;
    }
}

(2)操作文件系统的工具类FileUtil:

import android.content.Context;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * 操作内存文件的工具类
 */
public class FileUtil {
    private static FileUtil instance;

    private Context context;

    private FileUtil(Context context) {
        this.context = context;
    }

    public static FileUtil getInstance(Context context) {
        if (instance == null) {
            synchronized (FileUtil.class) {
                if (instance == null) {
                    instance = new FileUtil(context);
                }
            }
        }
        return instance;
    }

    /**
     * 将文件存储到内存中
     */
    public void writeFileToStorage(String fileName, byte[] b) {
        FileOutputStream fos = null;
        try {
            File file = new File(context.getFilesDir(), fileName);
            fos = new FileOutputStream(file);
            fos.write(b, 0, b.length);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 从内存中读取文件的字节码
     */
    public byte[] readBytesFromStorage(String fileName) {
        byte[] b = null;
        FileInputStream fis = null;
        ByteArrayOutputStream baos = null;
        try {
            fis = context.openFileInput(fileName);
            baos = new ByteArrayOutputStream();
            byte[] tmp = new byte[1024];
            int len = 0;
            while ((len = fis.read(tmp)) != -1) {
                baos.write(tmp, 0, len);
            }
            b = baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (baos != null) {
                    baos.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return b;
    }
}

(3)LruCache的子类ImageCache:

import android.graphics.Bitmap;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.LruCache;

import java.lang.ref.SoftReference;
import java.util.Map;

/**
 * 图片缓存
 */
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
public class ImageCache extends LruCache<String, Bitmap> {
    private Map<String, SoftReference<Bitmap>> cacheMap;

    public ImageCache(Map<String, SoftReference<Bitmap>> cacheMap) {
        super((int) (Runtime.getRuntime().maxMemory() / 8));
        this.cacheMap = cacheMap;
    }

    @Override // 获取图片大小
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override // 当有图片从LruCache中移除时,将其放进软引用集合中
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
        if (oldValue != null) {
            SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);
            cacheMap.put(key, softReference);
        }
    }

    public Map<String, SoftReference<Bitmap>> getCacheMap() {
        return cacheMap;
    }
}

(4)三级缓存的工具类CacheUtil:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.widget.ImageView;

import java.io.File;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;

/**
 * 缓存工具类
 */
public class CacheUtil {
    private static CacheUtil instance;

    private Context context;
    private ImageCache imageCache;

    private CacheUtil(Context context) {
        this.context = context;
        Map<String, SoftReference<Bitmap>> cacheMap = new HashMap<>();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { // SDK版本判断
            this.imageCache = new ImageCache(cacheMap);
        }
    }

    public static CacheUtil getInstance(Context context) {
        if (instance == null) {
            synchronized (CacheUtil.class) {
                if (instance == null) {
                    instance = new CacheUtil(context);
                }
            }
        }
        return instance;
    }

    /**
     * 将图片添加到缓存中
     */
    private void putBitmapIntoCache(String fileName, byte[] data) {
        // 将图片的字节数组写入到内存中
        FileUtil.getInstance(context).writeFileToStorage(fileName, data);
        // 将图片存入强引用(LruCache)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
            imageCache.put(fileName, BitmapFactory.decodeByteArray(data, 0, data.length));
        }
    }

    /**
     * 从缓存中取出图片
     */
    private Bitmap getBitmapFromCache(String fileName) {
        // 从强引用(LruCache)中取出图片
        Bitmap bm = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) { // SDK版本判断
            bm = imageCache.get(fileName);
            if (bm == null) {
                // 如果图片不存在强引用中,则去软引用(SoftReference)中查找
                Map<String, SoftReference<Bitmap>> cacheMap = imageCache.getCacheMap();
                SoftReference<Bitmap> softReference = cacheMap.get(fileName);
                if (softReference != null) {
                    bm = softReference.get();
                    imageCache.put(fileName, bm);
                } else {
                    // 如果图片不存在软引用中,则去内存中找
                    byte[] data = FileUtil.getInstance(context).readBytesFromStorage(fileName);
                    if (data != null && data.length > 0) {
                        bm = BitmapFactory.decodeByteArray(data, 0, data.length);
                        imageCache.put(fileName, bm);
                    }
                }
            }
        }
        return bm;
    }

    /**
     * 使用三级缓存为ImageView设置图片
     */
    public void setImageToView(final String path, final ImageView view) {
        final String fileName = path.substring(path.lastIndexOf(File.separator) + 1);
        Bitmap bm = getBitmapFromCache(fileName);
        if (bm != null) {
            view.setImageBitmap(bm);
        } else {
            // 从网络获取图片
            new Thread(new Runnable() {
                @Override
                public void run() {
                    byte[] b = HttpUtil.getInstance().getByteArrayFromWeb(path);
                    if (b != null && b.length > 0) {
                        // 将图片字节数组写入到缓存中
                        putBitmapIntoCache(fileName, b);
                        final Bitmap bm = BitmapFactory.decodeByteArray(b, 0, b.length);
                        // 将从网络获取到的图片设置给ImageView
                        view.post(new Runnable() {
                            @Override
                            public void run() {
                                view.setImageBitmap(bm);
                            }
                        });
                    }
                }
            }).start();
        }
    }
}

3、调用

(1)MainActivity的布局文件activity_main.xml中的代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/id_main_lv_lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="#DDDDDD"
        android:dividerHeight="1.0dip" />

</RelativeLayout>

(2)MainActivity中的代码:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ListView;

import com.example.itgungnir.testimagecache.R;
import com.example.itgungnir.testimagecache.SharedData;
import com.example.itgungnir.testimagecache.adapter.ImageAdapter;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private ListView lv;

    private List<String> urlList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv = (ListView) findViewById(R.id.id_main_lv_lv);
        initData();
    }

    // 初始化数据
    private void initData() {
        // 初始化图片URL列表
        urlList = Arrays.asList(SharedData.IMAGE_URLS);
    }

    @Override
    protected void onResume() {
        super.onResume();
        initView();
    }

    // 初始化视图
    private void initView() {
        // 为ListView适配数据
        ImageAdapter adapter = new ImageAdapter(MainActivity.this, urlList);
        lv.setAdapter(adapter);
    }
}

(3)ListView的适配器类ImageAdapter中的代码:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ListView;

import com.example.itgungnir.testimagecache.R;
import com.example.itgungnir.testimagecache.SharedData;
import com.example.itgungnir.testimagecache.adapter.ImageAdapter;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private ListView lv;

    private List<String> urlList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv = (ListView) findViewById(R.id.id_main_lv_lv);
        initData();
    }

    // 初始化数据
    private void initData() {
        // 初始化图片URL列表
        urlList = Arrays.asList(SharedData.IMAGE_URLS);
    }

    @Override
    protected void onResume() {
        super.onResume();
        initView();
    }

    // 初始化视图
    private void initView() {
        // 为ListView适配数据
        ImageAdapter adapter = new ImageAdapter(MainActivity.this, urlList);
        lv.setAdapter(adapter);
    }
}

(4)ListView的Item的布局文件listitem_image.xml中的代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10.0dip">

    <ImageView
        android:id="@+id/id_imageitem_image"
        android:layout_width="100.0dip"
        android:layout_height="100.0dip"
        android:layout_gravity="center_horizontal"
        android:contentDescription="@string/app_name"
        android:scaleType="fitXY" />

</LinearLayout>

https://www.cnblogs.com/itgungnir/p/6211002.html

https://www.jianshu.com/p/b49a111147ee

猜你喜欢

转载自blog.csdn.net/xujunfeng000/article/details/82587911