在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:
  1. import java.io.ByteArrayOutputStream;
  2. import java.io.InputStream;
  3. import java.net.HttpURLConnection;
  4. import java.net.URL;
  5.  
  6. /**
  7. * 访问Http的工具类
  8. */
  9. public class HttpUtil {
  10. private static HttpUtil instance;
  11.  
  12. private HttpUtil() {
  13. }
  14.  
  15. public static HttpUtil getInstance() {
  16. if (instance == null) {
  17. synchronized (HttpUtil.class) {
  18. if (instance == null) {
  19. instance = new HttpUtil();
  20. }
  21. }
  22. }
  23. return instance;
  24. }
  25.  
  26. /**
  27. * 通过path(URL)访问网络获取返回的字节数组
  28. */
  29. public byte[] getByteArrayFromWeb(String path) {
  30. byte[] b = null;
  31. InputStream is = null;
  32. ByteArrayOutputStream baos = null;
  33. try {
  34. URL url = new URL(path);
  35. HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  36. connection.setRequestMethod("GET");
  37. connection.setDoInput(true);
  38. connection.setConnectTimeout(5000);
  39. connection.connect();
  40. if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
  41. baos = new ByteArrayOutputStream();
  42. is = connection.getInputStream();
  43. byte[] tmp = new byte[1024];
  44. int length = 0;
  45. while ((length = is.read(tmp)) != -1) {
  46. baos.write(tmp, 0, length);
  47. }
  48. }
  49. b = baos.toByteArray();
  50. } catch (Exception e) {
  51. e.printStackTrace();
  52. } finally {
  53. try {
  54. if (is != null) {
  55. is.close();
  56. }
  57. if (baos != null) {
  58. baos.close();
  59. }
  60. } catch (Exception e) {
  61. e.printStackTrace();
  62. }
  63. }
  64. return b;
  65. }
  66. }

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

  1. import android.content.Context;
  2.  
  3. import java.io.ByteArrayOutputStream;
  4. import java.io.File;
  5. import java.io.FileInputStream;
  6. import java.io.FileOutputStream;
  7.  
  8. /**
  9. * 操作内存文件的工具类
  10. */
  11. public class FileUtil {
  12. private static FileUtil instance;
  13.  
  14. private Context context;
  15.  
  16. private FileUtil(Context context) {
  17. this.context = context;
  18. }
  19.  
  20. public static FileUtil getInstance(Context context) {
  21. if (instance == null) {
  22. synchronized (FileUtil.class) {
  23. if (instance == null) {
  24. instance = new FileUtil(context);
  25. }
  26. }
  27. }
  28. return instance;
  29. }
  30.  
  31. /**
  32. * 将文件存储到内存中
  33. */
  34. public void writeFileToStorage(String fileName, byte[] b) {
  35. FileOutputStream fos = null;
  36. try {
  37. File file = new File(context.getFilesDir(), fileName);
  38. fos = new FileOutputStream(file);
  39. fos.write(b, 0, b.length);
  40. } catch (Exception e) {
  41. e.printStackTrace();
  42. } finally {
  43. try {
  44. if (fos != null) {
  45. fos.close();
  46. }
  47. } catch (Exception e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. }
  52.  
  53. /**
  54. * 从内存中读取文件的字节码
  55. */
  56. public byte[] readBytesFromStorage(String fileName) {
  57. byte[] b = null;
  58. FileInputStream fis = null;
  59. ByteArrayOutputStream baos = null;
  60. try {
  61. fis = context.openFileInput(fileName);
  62. baos = new ByteArrayOutputStream();
  63. byte[] tmp = new byte[1024];
  64. int len = 0;
  65. while ((len = fis.read(tmp)) != -1) {
  66. baos.write(tmp, 0, len);
  67. }
  68. b = baos.toByteArray();
  69. } catch (Exception e) {
  70. e.printStackTrace();
  71. } finally {
  72. try {
  73. if (fis != null) {
  74. fis.close();
  75. }
  76. if (baos != null) {
  77. baos.close();
  78. }
  79. } catch (Exception e) {
  80. e.printStackTrace();
  81. }
  82. }
  83. return b;
  84. }
  85. }

(3)LruCache的子类ImageCache:

  1. import android.graphics.Bitmap;
  2. import android.os.Build;
  3. import android.support.annotation.RequiresApi;
  4. import android.util.LruCache;
  5.  
  6. import java.lang.ref.SoftReference;
  7. import java.util.Map;
  8.  
  9. /**
  10. * 图片缓存
  11. */
  12. @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
  13. public class ImageCache extends LruCache<String, Bitmap> {
  14. private Map<String, SoftReference<Bitmap>> cacheMap;
  15.  
  16. public ImageCache(Map<String, SoftReference<Bitmap>> cacheMap) {
  17. super((int) (Runtime.getRuntime().maxMemory() / 8));
  18. this.cacheMap = cacheMap;
  19. }
  20.  
  21. @Override // 获取图片大小
  22. protected int sizeOf(String key, Bitmap value) {
  23. return value.getRowBytes() * value.getHeight();
  24. }
  25.  
  26. @Override // 当有图片从LruCache中移除时,将其放进软引用集合中
  27. protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
  28. if (oldValue != null) {
  29. SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);
  30. cacheMap.put(key, softReference);
  31. }
  32. }
  33.  
  34. public Map<String, SoftReference<Bitmap>> getCacheMap() {
  35. return cacheMap;
  36. }
  37. }

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

  1. import android.content.Context;
  2. import android.graphics.Bitmap;
  3. import android.graphics.BitmapFactory;
  4. import android.os.Build;
  5. import android.widget.ImageView;
  6.  
  7. import java.io.File;
  8. import java.lang.ref.SoftReference;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11.  
  12. /**
  13. * 缓存工具类
  14. */
  15. public class CacheUtil {
  16. private static CacheUtil instance;
  17.  
  18. private Context context;
  19. private ImageCache imageCache;
  20.  
  21. private CacheUtil(Context context) {
  22. this.context = context;
  23. Map<String, SoftReference<Bitmap>> cacheMap = new HashMap<>();
  24. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { // SDK版本判断
  25. this.imageCache = new ImageCache(cacheMap);
  26. }
  27. }
  28.  
  29. public static CacheUtil getInstance(Context context) {
  30. if (instance == null) {
  31. synchronized (CacheUtil.class) {
  32. if (instance == null) {
  33. instance = new CacheUtil(context);
  34. }
  35. }
  36. }
  37. return instance;
  38. }
  39.  
  40. /**
  41. * 将图片添加到缓存中
  42. */
  43. private void putBitmapIntoCache(String fileName, byte[] data) {
  44. // 将图片的字节数组写入到内存中
  45. FileUtil.getInstance(context).writeFileToStorage(fileName, data);
  46. // 将图片存入强引用(LruCache)
  47. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
  48. imageCache.put(fileName, BitmapFactory.decodeByteArray(data, 0, data.length));
  49. }
  50. }
  51.  
  52. /**
  53. * 从缓存中取出图片
  54. */
  55. private Bitmap getBitmapFromCache(String fileName) {
  56. // 从强引用(LruCache)中取出图片
  57. Bitmap bm = null;
  58. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) { // SDK版本判断
  59. bm = imageCache.get(fileName);
  60. if (bm == null) {
  61. // 如果图片不存在强引用中,则去软引用(SoftReference)中查找
  62. Map<String, SoftReference<Bitmap>> cacheMap = imageCache.getCacheMap();
  63. SoftReference<Bitmap> softReference = cacheMap.get(fileName);
  64. if (softReference != null) {
  65. bm = softReference.get();
  66. imageCache.put(fileName, bm);
  67. } else {
  68. // 如果图片不存在软引用中,则去内存中找
  69. byte[] data = FileUtil.getInstance(context).readBytesFromStorage(fileName);
  70. if (data != null && data.length > 0) {
  71. bm = BitmapFactory.decodeByteArray(data, 0, data.length);
  72. imageCache.put(fileName, bm);
  73. }
  74. }
  75. }
  76. }
  77. return bm;
  78. }
  79.  
  80. /**
  81. * 使用三级缓存为ImageView设置图片
  82. */
  83. public void setImageToView(final String path, final ImageView view) {
  84. final String fileName = path.substring(path.lastIndexOf(File.separator) + 1);
  85. Bitmap bm = getBitmapFromCache(fileName);
  86. if (bm != null) {
  87. view.setImageBitmap(bm);
  88. } else {
  89. // 从网络获取图片
  90. new Thread(new Runnable() {
  91. @Override
  92. public void run() {
  93. byte[] b = HttpUtil.getInstance().getByteArrayFromWeb(path);
  94. if (b != null && b.length > 0) {
  95. // 将图片字节数组写入到缓存中
  96. putBitmapIntoCache(fileName, b);
  97. final Bitmap bm = BitmapFactory.decodeByteArray(b, 0, b.length);
  98. // 将从网络获取到的图片设置给ImageView
  99. view.post(new Runnable() {
  100. @Override
  101. public void run() {
  102. view.setImageBitmap(bm);
  103. }
  104. });
  105. }
  106. }
  107. }).start();
  108. }
  109. }
  110. }
 

3、调用

(1)MainActivity的布局文件activity_main.xml中的代码:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:id="@+id/activity_main"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent">
  6.  
  7. <ListView
  8. android:id="@+id/id_main_lv_lv"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"
  11. android:divider="#DDDDDD"
  12. android:dividerHeight="1.0dip" />
  13.  
  14. </RelativeLayout>

(2)MainActivity中的代码:

  1. import android.os.Bundle;
  2. import android.support.v7.app.AppCompatActivity;
  3. import android.widget.ListView;
  4.  
  5. import com.example.itgungnir.testimagecache.R;
  6. import com.example.itgungnir.testimagecache.SharedData;
  7. import com.example.itgungnir.testimagecache.adapter.ImageAdapter;
  8.  
  9. import java.util.Arrays;
  10. import java.util.List;
  11.  
  12. public class MainActivity extends AppCompatActivity {
  13. private ListView lv;
  14.  
  15. private List<String> urlList;
  16.  
  17. @Override
  18. protected void onCreate(Bundle savedInstanceState) {
  19. super.onCreate(savedInstanceState);
  20. setContentView(R.layout.activity_main);
  21. lv = (ListView) findViewById(R.id.id_main_lv_lv);
  22. initData();
  23. }
  24.  
  25. // 初始化数据
  26. private void initData() {
  27. // 初始化图片URL列表
  28. urlList = Arrays.asList(SharedData.IMAGE_URLS);
  29. }
  30.  
  31. @Override
  32. protected void onResume() {
  33. super.onResume();
  34. initView();
  35. }
  36.  
  37. // 初始化视图
  38. private void initView() {
  39. // 为ListView适配数据
  40. ImageAdapter adapter = new ImageAdapter(MainActivity.this, urlList);
  41. lv.setAdapter(adapter);
  42. }
  43. }

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

  1. import android.os.Bundle;
  2. import android.support.v7.app.AppCompatActivity;
  3. import android.widget.ListView;
  4.  
  5. import com.example.itgungnir.testimagecache.R;
  6. import com.example.itgungnir.testimagecache.SharedData;
  7. import com.example.itgungnir.testimagecache.adapter.ImageAdapter;
  8.  
  9. import java.util.Arrays;
  10. import java.util.List;
  11.  
  12. public class MainActivity extends AppCompatActivity {
  13. private ListView lv;
  14.  
  15. private List<String> urlList;
  16.  
  17. @Override
  18. protected void onCreate(Bundle savedInstanceState) {
  19. super.onCreate(savedInstanceState);
  20. setContentView(R.layout.activity_main);
  21. lv = (ListView) findViewById(R.id.id_main_lv_lv);
  22. initData();
  23. }
  24.  
  25. // 初始化数据
  26. private void initData() {
  27. // 初始化图片URL列表
  28. urlList = Arrays.asList(SharedData.IMAGE_URLS);
  29. }
  30.  
  31. @Override
  32. protected void onResume() {
  33. super.onResume();
  34. initView();
  35. }
  36.  
  37. // 初始化视图
  38. private void initView() {
  39. // 为ListView适配数据
  40. ImageAdapter adapter = new ImageAdapter(MainActivity.this, urlList);
  41. lv.setAdapter(adapter);
  42. }
  43. }

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

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. android:orientation="vertical"
  6. android:padding="10.0dip">
  7.  
  8. <ImageView
  9. android:id="@+id/id_imageitem_image"
  10. android:layout_width="100.0dip"
  11. android:layout_height="100.0dip"
  12. android:layout_gravity="center_horizontal"
  13. android:contentDescription="@string/app_name"
  14. android:scaleType="fitXY" />
  15.  
  16. </LinearLayout>

最终运行结果如下图所示:

【Android - 进阶】之图片三级缓存的原理及实现的更多相关文章

  1. Android 图片三级缓存之内存缓存(告别软引用(SoftRefrerence)和弱引用(WeakReference))

    因为之前项目同事使用了图片三级缓存,今天整理项目的时候发现同事还是使用了软引用(SoftRefrerence)和弱引用(WeakReference),来管理在内存中的缓存.看到这个我就感觉不对了.脑海 ...

  2. Android异步下载图片并且缓存图片到本地

    Android异步下载图片并且缓存图片到本地 在Android开发中我们经常有这样的需求,从服务器上下载xml或者JSON类型的数据,其中包括一些图片资源,本demo模拟了这个需求,从网络上加载XML ...

  3. Android进阶:七、Retrofit2.0原理解析之最简流程【下】

    紧接上文Android进阶:七.Retrofit2.0原理解析之最简流程[上] 一.请求参数整理 我们定义的接口已经被实现,但是我们还是不知道我们注解的请求方式,参数类型等是如何发起网络请求的呢? 这 ...

  4. Android 图片三级缓存

    图片缓存的原理 实现图片缓存也不难,需要有相应的cache策略.这里采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cac ...

  5. android读取大图片并缓存

    最近开发电视版的云存储应用,要求”我的相册“模块有全屏预览图片的功能,全屏分辨率是1920*1080超清.UI组件方面采用Gallery+ImageSwitcher组合,这里略过,详情参见google ...

  6. android对大图片的缓存处理

    废话不多说,直接上代码 package com.huge.emj.common.util; import java.io.File; import java.io.FileInputStream; i ...

  7. 【Android 进阶】图片载入框架之Glide

    简单介绍 在泰国举行的谷歌开发人员论坛上,谷歌为我们介绍了一个名叫 Glide 的图片载入库,作者是 bumptech.这个库被广泛的运用在 google 的开源项目中,包含 2014 年 googl ...

  8. Android进阶:七、Retrofit2.0原理解析之最简流程【上】

    retrofit 已经流行很久了,它是Square开源的一款优秀的网络框架,这个框架对okhttp进行了封装,让我们使用okhttp做网路请求更加简单.但是光学会使用只是让我们多了一个技能,学习其源码 ...

  9. listview 使用图片三级缓存图片闪动

随机推荐

  1. Mysql创建删除索引

    1.查看某个表中的索引 show index from 表名 2.为某个表创建索引 alter table 表名 add index 索引名(列名)    //此种方式创建一般的索引 alter ta ...

  2. 关于Apple设备私有的apple-touch-icon属性详解

    以前我们用过favicon在浏览器给网站进行身份标识,用法如下: <link href="http://image.feeliu.com/web/favicon.ico" r ...

  3. Android学习2--项目文件列表简单分析

    使用Eclipse创建的默认项目文件列表如下: src:src目录是Android工程的源程序目录,该目录用于存放Java项目的源代码 gen:gen目录存放所有自动生成的文件,在这个目录中最关键的文 ...

  4. 请大神帮忙解决 jquery 控制 li 标签问题

    <li class="active"><a href="#1" data-toggle="tab">日志详细情况&l ...

  5. 从用python自动生成.h的头文件集合和类声明集合到用python读写文件

    最近在用python自动生成c++的类.因为这些类会根据需求不同产生不同的类,所以需要用python自动生成.由于会产生大量的类,而且这些类是变化的.所以如果是在某个.h中要用include来加载这些 ...

  6. PI数据库的使用-PI System Management Tools

    1.PI连接管理器 2.标记搜索 3.当前值

  7. Java反射的理解

    反射的作用:   1.运行时检查类的结构 2.运行时更改类的字段值 3.调用类的方法   准备知识:   Class类:虚拟机为每一个对象保存的一份对象所属类的清单: static Class for ...

  8. 当今流行的 React.js 适用于怎样的 Web App?

    外村 和仁(株式会社 ピクセルグリッド)  React.js是什么? React.js是Facebook开发的框架. http://facebook.github.io/react/ 官网上的描述是「 ...

  9. 【UVALive - 3713】Astronauts (2-SAT)

    题意: 有n个宇航员,按照年龄划分,年龄低于平均年龄的是年轻宇航员,而年龄大于等于平均年龄的是老练的宇航员. 现在要分配他们去A,B,C三个空间站,其中A站只有老练的宇航员才能去,而B站是只有年轻的才 ...

  10. 在linux中使用php将word文档转为pdf

    使用本教程需要在linux中安装openoffice,改页面中有详细的安装与使用教程(http://www.cnblogs.com/sustudy/p/3999628.html). 既然,你看了该教程 ...