Android 图片缓存设计

1.简介

大家都知道,在我们Android 开发的过程中,对于图片的处理,是非常重要的,而对于我们如果每次都重网络去拉去图片,那样会造成,现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如淘宝,qq的照片墙)中比较多。

现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。

2.图片缓存的原理

实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。

关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。

3.实例源码

(1)内存缓存

package com.zengtao.tools;

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

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.LruCache;

/**
 * 内存缓存:两层缓存
 *
 * @author zengtao 2015年4月27日 上午10:39:23
 */
public class MemoryCache {

	private final static int SOFT_CACHE_SIZE = 15; // 软引用缓存容量
	private static LruCache<String, Bitmap> mLruCache; // 硬引用缓存
	private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; // 软引用缓存

	@SuppressLint("NewApi")
	public MemoryCache(Context context) {
		int memClass = ((ActivityManager) context
				.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
		int cacheSize = 1024 * 1024 * memClass / 4; // 获取系统的1/4的空间 作为缓存大小
		mLruCache = new LruCache<String, Bitmap>(cacheSize) {

			@Override
			protected int sizeOf(String key, Bitmap value) {
				if (value != null) {
					return value.getRowBytes() * value.getHeight();
				}
				return 0;
			}

			@Override
			protected void entryRemoved(boolean evicted, String key,
					Bitmap oldValue, Bitmap newValue) {
				if (oldValue != null) {
					// 硬引用缓存满的时候,会根据lru算法把最近没有被使用的图片抓入软引用
					mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
				}
			}
		};
		mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(
				SOFT_CACHE_SIZE, 0.75f, true) {

			private static final long serialVersionUID = 1L;

			@Override
			protected boolean removeEldestEntry(
					java.util.Map.Entry<String, SoftReference<Bitmap>> eldest) {
				if (size() > SOFT_CACHE_SIZE) {
					return true;
				}
				return false;
			}
		};
	}

	/**
	 * 存储图片到缓存
	 *
	 * @param url
	 *            :key
	 * @param bitmap
	 *            : 图片
	 */
	@SuppressLint("NewApi")
	public void saveBitmap(String url, Bitmap bitmap) {
		if (bitmap != null) {
			synchronized (bitmap) {
				mLruCache.put(url, bitmap);
			}
		}
	}

	/**
	 * 获取缓存图片
	 *
	 * @param url
	 *            :url
	 * @return
	 */
	@SuppressLint("NewApi")
	public Bitmap getBitmap(String url) {
		Bitmap bitmap = null;
		// 从硬引用找
		synchronized (mLruCache) {
			// 从硬引用中获取
			bitmap = mLruCache.get(url);
			if (bitmap != null) {
				// 如果找到了,将元素移动到linkendHashMap的最前面,从而保证lrd算法中的是最后删除
				mLruCache.remove(url);
				mLruCache.put(url, bitmap);
				return bitmap;
			}
		}
		// 硬引用没找到,从软引用找
		synchronized (mSoftCache) {
			SoftReference<Bitmap> softReference = mSoftCache.get(url);
			if (softReference != null) {
				bitmap = softReference.get();
				// 如果找到了,重新添加到硬缓存中
				mLruCache.put(url, bitmap);
				mSoftCache.remove(url);
				return bitmap;
			} else {
				mSoftCache.remove(url);
			}
		}
		return null;
	}

	public void clearCache() {
		mSoftCache.clear();
	}
}

(2)文件缓存

package com.zengtao.tools;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Comparator;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.StatFs;

/**
 * 文件缓存
 *
 * @author zengtao 2015年4月27日 上午11:49:52
 */
public class FileCache {
	private final static String IMAGECACHE = "ImageCache";
	private final static String lASTPATHNAME = ".cache"; // 文件名

	private final static int MB = 1024 * 1024;
	private final static int CACHESIZE = 10;
	private final static int SDCARD_FREE_SPANCE_CACHE = 10;

	public FileCache() {
		removeCache(getDirectory());
	}

	/**
	 * 将图片存入缓存
	 *
	 * @param url
	 *            : 地址
	 * @param bitmap
	 *            : 图片
	 */
	public void saveBitmap(String url, Bitmap bitmap) {
		if (bitmap == null) {
			return;
		}
		if (SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) {
			return; // 空间不足
		}
		String fileName = convertUrlToFileName(url);
		String dirPath = getDirectory();
		File dirFile = new File(dirPath);
		if (dirFile.exists()) {
			dirFile.mkdirs();
		}
		File file = new File(dirPath + "/" + fileName);
		try {
			file.createNewFile();
			OutputStream outputStream = new FileOutputStream(file);
			bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
			outputStream.flush();
			outputStream.close();
		} catch (Exception e) {
			System.out.println("文件未找到或者io异常");
		}
	}

	/**
	 * 获取文件缓存图片
	 *
	 * @param url
	 *            : 地址
	 * @return : bitmap
	 */
	public Bitmap getBitmap(final String url) {
		Bitmap bitmap = null;
		final String path = getDirectory() + convertUrlToFileName(url);
		File file = new File(path);
		if (file.exists()) {
			bitmap = BitmapFactory.decodeFile(path);
			if (bitmap == null) {
				file.delete();
			} else {
				updateFileTime(path);
			}
		}
		return bitmap;
	}

	/**
	 * 获取sdCard路径
	 *
	 * @return :路径地址
	 */
	private String getSDCardPath() {
		String path = "";
		File file = null;
		boolean isSDCardExist = Environment.getExternalStorageState()
				.toString().equals(android.os.Environment.MEDIA_MOUNTED); // 判断是否有sdCard
		if (isSDCardExist) {
			file = Environment.getExternalStorageDirectory();
		}
		if (file != null) {
			path = file.toString();
		}
		return path;
	}

	/**
	 * 计算存储目录下的文件大小,
	 * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
	 * 那么删除40%最近没有被使用的文件
	 */
	private boolean removeCache(String dirPath) {
		File dir = new File(dirPath);
		File[] files = dir.listFiles();
		if (files == null) {
			return true;
		}

		if (!android.os.Environment.getExternalStorageState().equals(
				android.os.Environment.MEDIA_MOUNTED)) {
			return false;
		}

		int dirSize = 0;
		for (int i = 0; i < files.length; i++) {
			if (files[i].getName().contains(lASTPATHNAME)) {
				dirSize += files[i].length();
			}
		}

		if (dirSize > CACHESIZE * MB
				|| SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) {
			int removeFactor = (int) ((0.4 * files.length) + 1);
			Arrays.sort(files, new FileLastModifSort());
			for (int i = 0; i < removeFactor; i++) {
				if (files[i].getName().contains(lASTPATHNAME)) {
					files[i].delete();
				}
			}
		}

		if (caluateSDCardFreeSpance() <= CACHESIZE) {
			return false;
		}

		return true;
	}

	/**
	 * 获取缓存目录
	 *
	 * @return : 目录
	 */
	private String getDirectory() {
		return getSDCardPath() + "/" + IMAGECACHE;
	}

	/**
	 * 将url转换成文件名
	 *
	 * @param url
	 *            : 地址
	 * @return : 文件名
	 */
	private String convertUrlToFileName(final String url) {
		String[] strs = url.split("/");
		return strs[strs.length - 1] + lASTPATHNAME;
	}

	/**
	 * 计算sdCard上的空闲空间
	 *
	 * @return : 大小
	 */
	@SuppressLint("NewApi")
	private int caluateSDCardFreeSpance() {
		int freespance = 0;
		StatFs start = new StatFs(Environment.getExternalStorageDirectory()
				.getPath());
		long blocksize = start.getBlockSizeLong();
		long availableBlocks = start.getAvailableBlocksLong();
		freespance = Integer.parseInt(blocksize * availableBlocks + "");
		return freespance;
	}

	/** 修改文件的最后修改时间 **/
	public void updateFileTime(String path) {
		File file = new File(path);
		long lastTime = System.currentTimeMillis();
		file.setLastModified(lastTime);
	}

	/** 根据文件的最后修改时间进行排序 **/
	private class FileLastModifSort implements Comparator<File> {
		public int compare(File arg0, File arg1) {
			if (arg0.lastModified() > arg1.lastModified()) {
				return 1;
			} else if (arg0.lastModified() == arg1.lastModified()) {
				return 0;
			} else {
				return -1;
			}
		}
	}
}

(3)http缓存

package com.zengtao.tools;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

/**
 * 网络缓存
 *
 * @author zengtao 2015年4月27日 下午3:32:34
 */
public class HttpCache {
	private static final String LOG_TAG = "ImageGetFromHttp";

	public static Bitmap downloadBitmap(String url) {
		final HttpClient client = new DefaultHttpClient();
		final HttpGet getRequest = new HttpGet(url);

		try {
			HttpResponse response = client.execute(getRequest);
			final int statusCode = response.getStatusLine().getStatusCode();
			if (statusCode != HttpStatus.SC_OK) {
				Log.w(LOG_TAG, "Error " + statusCode
						+ " while retrieving bitmap from " + url);
				return null;
			}

			final HttpEntity entity = response.getEntity();
			if (entity != null) {
				InputStream inputStream = null;
				try {
					inputStream = entity.getContent();
					FilterInputStream fit = new FlushedInputStream(inputStream);
					return BitmapFactory.decodeStream(fit);
				} finally {
					if (inputStream != null) {
						inputStream.close();
						inputStream = null;
					}
					entity.consumeContent();
				}
			}
		} catch (IOException e) {
			getRequest.abort();
			Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
		} catch (IllegalStateException e) {
			getRequest.abort();
			Log.w(LOG_TAG, "Incorrect URL: " + url);
		} catch (Exception e) {
			getRequest.abort();
			Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
		} finally {
			client.getConnectionManager().shutdown();
		}
		return null;
	}

	/**
	 * InputStream流有个小bug在慢速网络的情况下可能产生中断,可以考虑重写FilterInputStream处理skip方法来解决这个bug
	 * BitmapFactory类的decodeStream方法在网络超时或较慢的时候无法获取完整的数据,这里我
	 * 们通过继承FilterInputStream类的skip方法来强制实现flush流中的数据
	 * ,主要原理就是检查是否到文件末端,告诉http类是否继续。
	 *
	 * @author zengtao 2015年4月27日 下午6:33:17
	 */
	static class FlushedInputStream extends FilterInputStream {
		public FlushedInputStream(InputStream inputStream) {
			super(inputStream);
		}

		@Override
		public long skip(long n) throws IOException {
			long totalBytesSkipped = 0L;
			while (totalBytesSkipped < n) {
				long bytesSkipped = in.skip(n - totalBytesSkipped);
				if (bytesSkipped == 0L) {
					int b = read();
					if (b < 0) {
						break; // we reached EOF
					} else {
						bytesSkipped = 1; // we read one byte
					}
				}
				totalBytesSkipped += bytesSkipped;
			}
			return totalBytesSkipped;
		}
	}
}

(4)主函数中调用

@SuppressLint("HandlerLeak")
	private Handler handler = new Handler() {
		public void handleMessage(Message msg) {
			if (msg.arg1 == 0x1) {
				if (msg.obj != null) {
					image.setImageBitmap((Bitmap) msg.obj);
				}
			}
		};
	};

	class MyThread extends Thread {
		@Override
		public void run() {
			bitmap = getBitmap("http://a.hiphotos.baidu.com/image/pic/item/77c6a7efce1b9d16f5022b7ef1deb48f8d5464e3.jpg");
			Message message = new Message();
			message.arg1 = 0x1;
			message.obj = bitmap;
			handler.sendMessage(message);
		}
	}

	/*** 获得一张图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/
	public Bitmap getBitmap(String url) {
		// 1.从内存缓存中获取图片
		Bitmap resultBitmap = memoryCache.getBitmap(url);
		if (resultBitmap == null) {
			// 2.文件缓存中获取
			resultBitmap = fileCache.getBitmap(url);
			if (resultBitmap == null) {
				// 3.从网络获取
				resultBitmap = HttpCache.downloadBitmap(url);
				if (resultBitmap != null) {
					fileCache.saveBitmap(url, resultBitmap);
					memoryCache.saveBitmap(url, resultBitmap);
					System.out.println("3.网络缓存中获取图片");
				}
			} else {
				// 添加到内存缓存
				memoryCache.saveBitmap(url, resultBitmap);
				System.out.println("2.文件缓存中获取图片");
			}
		} else {
			System.out.println("1.内存缓存中获取图片");
		}
		return resultBitmap;
	}

4.总结

以上就完成了一套缓存的设计,值得注意的是,当去网络获取图片的时候,图片过于庞大,一定要做去异步线程中获取图片,或者做本地缓存,这样不会让用户感觉自己的app卡死,是的用户体验效果更加。

时间: 2024-10-21 03:48:23

Android 图片缓存设计的相关文章

Android图片缓存技术!直接用在你的项目中,简单方便高效

好久没有写博客了,最近比较繁琐,什么面试呀,找工作呀,大三的生活就快完了,准确的是大学的生活就快完了!三年了,一直在搞移动开发,感觉好快呀,不想就这样的离开学校了!咳咳咳,扯远了... 前不久搞的Android图片缓存,刚开始引入开源的框架,用着还行,但是在开发中遇到问题,就比如universal-image-loader-1.9.5.jar这个框架吧,在加载图片的时候自定义imageview无法加载,可能是存在以下问题吧,况且导入框架导致开发的项目包越来越大,基于上面的这几种情况,于是我就想自

android图片缓存框架Android-Universal-Image-Loader(二)

这篇打算直接告诉大家怎么用吧,其实这个也不是很难的框架,大致使用过程如下: // 获取缓存图片目录 File cacheDir = StorageUtils.getOwnCacheDirectory(activity, "imageloader/Cache"); ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder( activity).memoryCacheExtraOptions(800, 76

android图片缓存框架Android-Universal-Image-Loader

最近跟同学们做了一个创业项目,其实跟以前做项目不同,以前大多数都是做web网站,对内存管理这些不太关注,因为是pc机,做android的话也就是一些小列子,现在到了做产品阶段吧,只能把项目做到最优.不扯了,先来说这个框架是做什么的,Android-Universal-Image-Loader主要是一个图片的缓存框架,根据官方解释就是,它提供了一个异步处理图片的方案.它提供两种获取图片方式async or sync,并且提供了一个可定制化的组件(thread executors, download

图片会说话系列之Android图片缓存框架

前言:看过很多精彩的文章,作者写的非常好,但总觉得文字描述没有图片或图表说明来得直观,因为图片可以化抽象为具体.语言是有区域性的,而图片则是全世界通用的,即使语言不通,却能通过图片了解一切.因此想做一个系列的简博客,内容就是一张图附带一些必要的说明,这样就能迅速抓住重点,而不需要做太多的阅读,便能了解框架性的东西.文章所涉及到的图片有的可能来自官方网站,有的来自名家博客,或者是自己绘制的,都会一一说明. 切入正题,先来第一发,上图: 上图来自:https://github.com/nostra1

Android图片缓存分析(一)

Android中写应用时,经常会遇到加载图片的事,由于很多图片是网络上下载获取的,当我们进页面时,便会去网络下载图片,一两次可能没啥问题,但如果同一张图片每次都去网络拉取,不仅速度慢,更影响用户体验,同时会浪费用户的流量. 基于此,很多人便想到了图片缓存的方法. 现在比较普遍的图片缓存主要有以下几个步骤: 一.从缓存中获取图片 二.如果缓存中未获取图片,则从存储卡中获取 三.如果存储卡中未获取图片,则从网络中获取 一.从缓存中获取图片 我们知道,Android中分配给每个应用的内存空间是有限的,

Android图片缓存库使用经验总结

Volley,Universal-Image-Loader和picasso 几个图片加载请求 框架的分析 http://www.wl566.com/biancheng/154046.html http://blog.csdn.net/djun100/article/details/24708825 主要是 在 ListView 滑动的时候,取消已经消失的,省下资源. Picasso 和 Universal-Image-Loader 都做了. volley 取消 好像要我们自己去取消, cancl

Android 图片缓存机制

1.采用线程池 2.内存缓存+文件缓存 3.内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4 4.对下载的图片进行按比例缩放,以减少内存的消耗 具体的代码里面说明.先放上内存缓存类的代码MemoryCache.java: public class MemoryCache { private static final String TAG = "MemoryCache"; // 放入缓存时是个同步操作 // LinkedHashM

android图片缓存(包含ReusableBitmapDrawable和BitmapPool)

现在做的项目中,有用到一个开源的2D地图框架osmdroid,但是在项目中,使用还是有一些问题,例如,多个地图实例,会有独自的图片缓存,Activity onPause时,并不会释放图片缓存,如果多级界面都有地图的话,可能会造成很多手机内存溢出(按照每个瓦片256*256,屏幕1280*720来算,显示一个屏幕的地图,至少要在内存保存15张图片,占用内存256*256*4*15=3.75M),所以还是得针对项目做一定的修改调整.今天将项目中使用到的图片缓存做一个整理. 首先说下整体的图片加载流程

Android图片缓存

Android基于universal-image-loader-1.9.4.jar的图片缓存. jar包可以去网上下载,其中封装了网络获取图片及在手机开辟缓存区域的方法,只需要做如下配置操作就可以使用. 它的作用方式是这样的,如果手机连接网络,那么该组件会从网络获取图片并将图片写入缓存,如果没有连接网络,那么会从缓存区域获取已经加载的图片并显示出来. 一下是具体的配置和使用方式: 1:添加jar包,并添加依赖. 2:配置universal-image-loader,代码: import com.