【Android - 进阶】之图片三级缓存的原理及实现
在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、实现
- 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、调用
- <?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 - 进阶】之图片三级缓存的原理及实现的更多相关文章
- Android 图片三级缓存之内存缓存(告别软引用(SoftRefrerence)和弱引用(WeakReference))
因为之前项目同事使用了图片三级缓存,今天整理项目的时候发现同事还是使用了软引用(SoftRefrerence)和弱引用(WeakReference),来管理在内存中的缓存.看到这个我就感觉不对了.脑海 ...
- Android异步下载图片并且缓存图片到本地
Android异步下载图片并且缓存图片到本地 在Android开发中我们经常有这样的需求,从服务器上下载xml或者JSON类型的数据,其中包括一些图片资源,本demo模拟了这个需求,从网络上加载XML ...
- Android进阶:七、Retrofit2.0原理解析之最简流程【下】
紧接上文Android进阶:七.Retrofit2.0原理解析之最简流程[上] 一.请求参数整理 我们定义的接口已经被实现,但是我们还是不知道我们注解的请求方式,参数类型等是如何发起网络请求的呢? 这 ...
- Android 图片三级缓存
图片缓存的原理 实现图片缓存也不难,需要有相应的cache策略.这里采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cac ...
- android读取大图片并缓存
最近开发电视版的云存储应用,要求”我的相册“模块有全屏预览图片的功能,全屏分辨率是1920*1080超清.UI组件方面采用Gallery+ImageSwitcher组合,这里略过,详情参见google ...
- android对大图片的缓存处理
废话不多说,直接上代码 package com.huge.emj.common.util; import java.io.File; import java.io.FileInputStream; i ...
- 【Android 进阶】图片载入框架之Glide
简单介绍 在泰国举行的谷歌开发人员论坛上,谷歌为我们介绍了一个名叫 Glide 的图片载入库,作者是 bumptech.这个库被广泛的运用在 google 的开源项目中,包含 2014 年 googl ...
- Android进阶:七、Retrofit2.0原理解析之最简流程【上】
retrofit 已经流行很久了,它是Square开源的一款优秀的网络框架,这个框架对okhttp进行了封装,让我们使用okhttp做网路请求更加简单.但是光学会使用只是让我们多了一个技能,学习其源码 ...
- listview 使用图片三级缓存图片闪动
随机推荐
- Mysql创建删除索引
1.查看某个表中的索引 show index from 表名 2.为某个表创建索引 alter table 表名 add index 索引名(列名) //此种方式创建一般的索引 alter ta ...
- 关于Apple设备私有的apple-touch-icon属性详解
以前我们用过favicon在浏览器给网站进行身份标识,用法如下: <link href="http://image.feeliu.com/web/favicon.ico" r ...
- Android学习2--项目文件列表简单分析
使用Eclipse创建的默认项目文件列表如下: src:src目录是Android工程的源程序目录,该目录用于存放Java项目的源代码 gen:gen目录存放所有自动生成的文件,在这个目录中最关键的文 ...
- 请大神帮忙解决 jquery 控制 li 标签问题
<li class="active"><a href="#1" data-toggle="tab">日志详细情况&l ...
- 从用python自动生成.h的头文件集合和类声明集合到用python读写文件
最近在用python自动生成c++的类.因为这些类会根据需求不同产生不同的类,所以需要用python自动生成.由于会产生大量的类,而且这些类是变化的.所以如果是在某个.h中要用include来加载这些 ...
- PI数据库的使用-PI System Management Tools
1.PI连接管理器 2.标记搜索 3.当前值
- Java反射的理解
反射的作用: 1.运行时检查类的结构 2.运行时更改类的字段值 3.调用类的方法 准备知识: Class类:虚拟机为每一个对象保存的一份对象所属类的清单: static Class for ...
- 当今流行的 React.js 适用于怎样的 Web App?
外村 和仁(株式会社 ピクセルグリッド) React.js是什么? React.js是Facebook开发的框架. http://facebook.github.io/react/ 官网上的描述是「 ...
- 【UVALive - 3713】Astronauts (2-SAT)
题意: 有n个宇航员,按照年龄划分,年龄低于平均年龄的是年轻宇航员,而年龄大于等于平均年龄的是老练的宇航员. 现在要分配他们去A,B,C三个空间站,其中A站只有老练的宇航员才能去,而B站是只有年轻的才 ...
- 在linux中使用php将word文档转为pdf
使用本教程需要在linux中安装openoffice,改页面中有详细的安装与使用教程(http://www.cnblogs.com/sustudy/p/3999628.html). 既然,你看了该教程 ...