在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>

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

【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. 虚拟化技术与"云"

    虚拟化技术: 如网站在某一时间访问量大,平时访问量少,如果一直保持大量的服务器提供服务,显示效率好低,浪费资源,在 不增减服务器,存储设备,网络等实际物理设备,而是利用软件将这些物理设备虚拟化,在有必 ...

  2. Bayeux协议

    Bayeux 协议-- Bayeux 1.0草案1 本备忘录状态 This document specifies a protocol for the Internet community, and ...

  3. Python【第十篇】协程、异步IO

    大纲 Gevent协程 阻塞IO和非阻塞IO.同步IO和异步IO的区别 事件驱动.IO多路复用(select/poll/epoll) 1.协程 1.1协程的概念 协程,又称微线程,纤程.英文名Coro ...

  4. python调用java

    这么个标题多少有点蛋疼的感觉,两个都是互联网时代的语言,学习成本和执行效率也差不多,之所以会产生这种需求,多半是想在python中引用java的类,例如安卓和hadoop的生态圈,基本是java代码的 ...

  5. 同步的HTTP请求

    代码: #import <Foundation/Foundation.h> void request(NSString *urlString) { NSLog(@"BEGIN&q ...

  6. PAT (Basic Level) 1002. 写出这个数 (20)

    读入一个自然数n,计算其各位数字之和,用汉语拼音写出和的每一位数字. 输入格式:每个测试输入包含1个测试用例,即给出自然数n的值.这里保证n小于10100. 输出格式:在一行内输出n的各位数字之和的每 ...

  7. Google将向IETF标准提交QUIC协议提案

    Google近期宣布,他们将向IETF提交实验性传输层网络协议QUIC的提案.此外,Google已经给出了QUIC协议优化页面加载时间的第一手数据. 自从2013年引入QUIC以来,Google一直在 ...

  8. Github、Jekyll 搭建及优化静态博客方法指南

    尝试自己写 Blog 的人,一般会经历三个阶段. 第一阶段,刚接触 Blog,觉得很新鲜,试着选择一个免费空间来写. 第二阶段,发现免费空间限制太多,就自己购买域名和空间,搭建独立博客. 第三阶段,觉 ...

  9. Bluetooth LE(低功耗蓝牙) - 第四部分

    回顾 在本系列前几篇文章中我们完成了BLE设备的发现 , 为我们的app通过BLE显示从TI SensorTag设备中获取到环境温度和湿度的工作打下了基础.在这篇文章中我们将着眼于连接到我们所发现的S ...

  10. Traffic Manager:Azure中国版 正式发布

     我们很高兴地宣布Azure Traffic Manager 现已面向中国版Azure正式发布.此版本现已投入生产,由企业 SLA支持,随时可用于生产场景中. 借助Azure Traffic Ma ...