disklrucache用法支持高并发输入数据吗

1070人阅读
Android(58)
DiskLruCache是谷歌推荐的用来实现硬盘缓存的类,本案例将对DiskLruCache的基本用法做一个总结,包括:创建缓存、查找使用缓存、移除缓存等等。
实现效果图
创建DiskLruCache
DiskLruCache使用open方法创建一个实例,如下所示:对应的四个参数分别为:缓存目录、应用版本号、单个key对应的数据的个数(一般设为1)、缓存的总大小,其中key是图片的url经过MD5转码获得的,防止url带有特殊符号影响缓存的正常使用。
File cacheDir = getDiskCacheDir(this, "bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(this), 1, 10 * 1024 * 1024);
} catch (Exception e) {
e.printStackTrace();
通过DiskLruCache.Editor可以获取到缓存对象的编辑对象editor,类似于SharedPreference的editor。DiskLruCache不允许同时编辑一个缓存对象,如果这个缓存对象正在被编辑,则editor==null。
new Thread(new Runnable() {
public void run() {
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Editor editor = null;
editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.abort();
mDiskLruCache.flush();
handler.post(new Runnable() {
public void run() {
textView.setText("saveCache done,the bitmap is ready");
} catch (IOException e) {
e.printStackTrace();
}).start();
DiskLruCache.get(String key)方法可以获取到一个Snapshot实例,通过DiskLruCache.Snapshot实例获取缓存文件的输入流,即可在imageView上显示该图片。
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(hashKeyForDisk(imageUrl));
if (snapshot != null) {
InputStream is = snapshot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
imageView.setImageBitmap(bitmap);
imageView.setImageBitmap(null);
} catch (IOException e) {
e.printStackTrace();
另外,DiskLruCache还提供了清空缓存delete()、获取缓存目录下的缓存大小size()等方法操作缓存。
本案例源码包含了DiskLruCache的基本使用方法,。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:99538次
积分:2115
积分:2115
排名:第13330名
原创:73篇
评论:86条
wechat: holy0729
github: /leoleohan
文章:23篇
阅读:67680
文章:32篇
阅读:182631159人阅读
android(70)
本文是对网络上几篇文章的综合,
Android照片墙完整版,完美结合LruCache和DiskLruCache - 郭霖的专栏 - 博客频道 - CSDN.NET
Android之ListView异步加载网络图片(优化缓存机制) - ZircoN的专栏 - 博客频道 - CSDN.NET
第一部分,使用LruCache和DiskLruCache:
新建一个Android项目,起名叫PhotoWallDemo,这里我使用的是Android 4.0的API。然后新建一个libcore.io包,并将DiskLruCache.java文件拷贝到这个包下,这样就把准备工作完成了。
接下来首先需要考虑的仍然是图片源的问题,简单起见,我仍然是吧所有图片都上传到了我的CSDN相册当中,然后新建一个Images类,将所有相册中图片的网址都配置进去,代码如下所示:
public class Images {
public final static String[] imageThumbUrls = new String[] {
&http://img.my.csdn.net/uploads//_1976.jpg&,
&http://img.my.csdn.net/uploads//_6518.jpg&,
&http://img.my.csdn.net/uploads//_8239.jpg&,
&http://img.my.csdn.net/uploads//_9329.jpg&,
&http://img.my.csdn.net/uploads//_1042.jpg&,
&http://img.my.csdn.net/uploads//_3977.jpg&,
&http://img.my.csdn.net/uploads//_8550.jpg&,
&http://img.my.csdn.net/uploads//_3954.jpg&,
&http://img.my.csdn.net/uploads//_4787.jpg&,
&http://img.my.csdn.net/uploads//_8243.jpg&,
&http://img.my.csdn.net/uploads//_3693.jpg&,
&http://img.my.csdn.net/uploads//_5120.jpg&,
&http://img.my.csdn.net/uploads//_3127.jpg&,
&http://img.my.csdn.net/uploads//_9576.jpg&,
&http://img.my.csdn.net/uploads//_1721.jpg&,
&http://img.my.csdn.net/uploads//_5806.jpg&,
&http://img.my.csdn.net/uploads//_7794.jpg&,
&http://img.my.csdn.net/uploads//_4418.jpg&,
&http://img.my.csdn.net/uploads//_3557.jpg&,
&http://img.my.csdn.net/uploads//_8779.jpg&,
&http://img.my.csdn.net/uploads//_4577.jpg&,
&http://img.my.csdn.net/uploads//_3407.jpg&,
&http://img.my.csdn.net/uploads//_2224.jpg&,
&http://img.my.csdn.net/uploads//_7301.jpg&,
&http://img.my.csdn.net/uploads//_7197.jpg&,
&http://img.my.csdn.net/uploads//_8410.jpg&,
&http://img.my.csdn.net/uploads//_3736.jpg&,
&http://img.my.csdn.net/uploads//_5094.jpg&,
&http://img.my.csdn.net/uploads//_7393.jpg&,
&http://img.my.csdn.net/uploads//_8813.jpg&,
&http://img.my.csdn.net/uploads//_3554.jpg&,
&http://img.my.csdn.net/uploads//_7894.jpg&,
&http://img.my.csdn.net/uploads//_2432.jpg&,
&http://img.my.csdn.net/uploads//_3071.jpg&,
&http://img.my.csdn.net/uploads//_3119.jpg&,
&http://img.my.csdn.net/uploads//_6589.jpg&,
&http://img.my.csdn.net/uploads//_8814.jpg&,
&http://img.my.csdn.net/uploads//_2237.jpg&,
&http://img.my.csdn.net/uploads//_4330.jpg&,
&http://img.my.csdn.net/uploads//_3602.jpg&,
&http://img.my.csdn.net/uploads//_3079.jpg&,
&http://img.my.csdn.net/uploads//_8125.jpg&,
&http://img.my.csdn.net/uploads//_4881.jpg&,
&http://img.my.csdn.net/uploads//_4559.jpg&,
&http://img.my.csdn.net/uploads//_3845.jpg&,
&http://img.my.csdn.net/uploads//_8955.jpg&,
&http://img.my.csdn.net/uploads//_2141.jpg&,
&http://img.my.csdn.net/uploads//_8437.jpg&,
&http://img.my.csdn.net/uploads//_6166.jpg&,
&http://img.my.csdn.net/uploads//_4843.jpg&,
&http://img.my.csdn.net/uploads//_5804.jpg&,
&http://img.my.csdn.net/uploads//_3362.jpg&,
&http://img.my.csdn.net/uploads//_2312.jpg&,
&http://img.my.csdn.net/uploads//_4960.jpg&,
&http://img.my.csdn.net/uploads//_2418.jpg&,
&http://img.my.csdn.net/uploads//_4490.jpg&,
&http://img.my.csdn.net/uploads//_5935.jpg&,
&http://img.my.csdn.net/uploads//_3865.jpg&,
&http://img.my.csdn.net/uploads//_4662.jpg&,
&http://img.my.csdn.net/uploads//_2553.jpg&,
&http://img.my.csdn.net/uploads//_5375.jpg&,
&http://img.my.csdn.net/uploads//_1748.jpg&,
&http://img.my.csdn.net/uploads//_7618.jpg&,
&http://img.my.csdn.net/uploads//_8606.jpg&,
&http://img.my.csdn.net/uploads//_8949.jpg&,
&http://img.my.csdn.net/uploads//_9821.jpg&,
&http://img.my.csdn.net/uploads//_6603.jpg&,
&http://img.my.csdn.net/uploads//_2405.jpg&,
&http://img.my.csdn.net/uploads//_6354.jpg&,
&http://img.my.csdn.net/uploads//_5779.jpg&,
&http://img.my.csdn.net/uploads//_7578.jpg&,
&http://img.my.csdn.net/uploads//_2436.jpg&,
&http://img.my.csdn.net/uploads//_3883.jpg&,
&http://img.my.csdn.net/uploads//_6269.jpg&,
&http://img.my.csdn.net/uploads//_4179.jpg&,
&http://img.my.csdn.net/uploads//_8326.jpg&,
&http://img.my.csdn.net/uploads//_7174.jpg&,
&http://img.my.csdn.net/uploads//_5170.jpg&,
&http://img.my.csdn.net/uploads//_4118.jpg&,
&http://img.my.csdn.net/uploads//_9532.jpg&,
&http://img.my.csdn.net/uploads//_3184.jpg&,
&http://img.my.csdn.net/uploads//_4772.jpg&,
&http://img.my.csdn.net/uploads//_4924.jpg&,
&http://img.my.csdn.net/uploads//_5762.jpg&,
&http://img.my.csdn.net/uploads//_7341.jpg&
设置好了图片源之后,我们需要一个GridView来展示照片墙上的每一张图片。打开或修改activity_main.xml中的代码,如下所示:
&LinearLayout xmlns:android=&/apk/res/android&
xmlns:tools=&/tools&
android:layout_width=&match_parent&
android:layout_height=&match_parent& &
android:id=&@+id/photo_wall&
android:layout_width=&match_parent&
android:layout_height=&match_parent&
android:columnWidth=&@dimen/image_thumbnail_size&
android:gravity=&center&
android:horizontalSpacing=&@dimen/image_thumbnail_spacing&
android:numColumns=&auto_fit&
android:stretchMode=&columnWidth&
android:verticalSpacing=&@dimen/image_thumbnail_spacing& &
&/GridView&
&/LinearLayout&
很简单,只是在LinearLayout中写了一个GridView而已。接着我们要定义GridView中每一个子View的布局,新建一个photo_layout.xml布局,加入如下代码:
&RelativeLayout xmlns:android=&/apk/res/android&
xmlns:tools=&/tools&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content& &
&ImageView
android:id=&@+id/photo&
android:layout_width=&match_parent&
android:layout_height=&match_parent&
android:layout_centerInParent=&true&
android:scaleType=&fitXY&
&/RelativeLayout&
仍然很简单,photo_layout.xml布局中只有一个ImageView控件,就是用它来显示图片的。这样我们就把所有的布局文件都写好了。
接下来新建PhotoWallAdapter做为GridView的适配器,代码如下所示:
public class PhotoWallAdapter extends ArrayAdapter&String& {
* 记录所有正在下载或等待下载的任务。
private Set&BitmapWorkerTask& taskC
* 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
private LruCache&String, Bitmap& mMemoryC
* 图片硬盘缓存核心类。
private DiskLruCache mDiskLruC
* GridView的实例
private GridView mPhotoW
* 记录每个子项的高度。
private int mItemHeight = 0;
public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects,
GridView photoWall) {
super(context, textViewResourceId, objects);
mPhotoWall = photoW
taskCollection = new HashSet&BitmapWorkerTask&();
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
mMemoryCache = new LruCache&String, Bitmap&(cacheSize) {
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
// 获取图片缓存路径
File cacheDir = getDiskCacheDir(context, &thumb&);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
// 创建DiskLruCache实例,初始化缓存数据
mDiskLruCache = DiskLruCache
.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
public View getView(int position, View convertView, ViewGroup parent) {
final String url = getItem(position);
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);
view = convertV
final ImageView imageView = (ImageView) view.findViewById(R.id.photo);
if (imageView.getLayoutParams().height != mItemHeight) {
imageView.getLayoutParams().height = mItemH
// 给ImageView设置一个Tag,保证异步加载图片时不会乱序
imageView.setTag(url);
imageView.setImageResource(R.drawable.empty_photo);
loadBitmaps(imageView, url);
* 将一张图片存储到LruCache中。
* @param key
LruCache的键,这里传入图片的URL地址。
* @param bitmap
LruCache的键,这里传入从网络上下载的Bitmap对象。
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
* 从LruCache中获取一张图片,如果不存在就返回null。
* @param key
LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的Bitmap对象,或者null。
public Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
* 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
* 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
public void loadBitmaps(ImageView imageView, String imageUrl) {
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (bitmap == null) {
BitmapWorkerTask task = new BitmapWorkerTask();
taskCollection.add(task);
task.execute(imageUrl);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
} catch (Exception e) {
e.printStackTrace();
* 取消所有正在下载或等待下载的任务。
public void cancelAllTasks() {
if (taskCollection != null) {
for (BitmapWorkerTask task : taskCollection) {
task.cancel(false);
* 根据传入的uniqueName获取硬盘缓存的路径地址。
public File getDiskCacheDir(Context context, String uniqueName) {
String cacheP
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
cachePath = context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
* 获取当前应用程序的版本号。
public int getAppVersion(Context context) {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),
return info.versionC
} catch (NameNotFoundException e) {
e.printStackTrace();
* 设置item子项的高度。
public void setItemHeight(int height) {
if (height == mItemHeight) {
mItemHeight =
notifyDataSetChanged();
* 使用MD5算法对传入的key进行加密并返回。
public String hashKeyForDisk(String key) {
String cacheK
final MessageDigest mDigest = MessageDigest.getInstance(&MD5&);
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
return cacheK
* 将缓存记录同步到journal文件中。
public void fluchCache() {
if (mDiskLruCache != null) {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i & bytes. i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
sb.append(hex);
return sb.toString();
* 异步下载图片的任务。
* @author guolin
class BitmapWorkerTask extends AsyncTask&String, Void, Bitmap& {
* 图片的URL地址
private String imageU
protected Bitmap doInBackground(String... params) {
imageUrl = params[0];
FileDescriptor fileDescriptor =
FileInputStream fileInputStream =
Snapshot snapShot =
// 生成图片URL对应的key
final String key = hashKeyForDisk(imageUrl);
// 查找key对应的缓存
snapShot = mDiskLruCache.get(key);
if (snapShot == null) {
// 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.abort();
// 缓存被写入后,再次查找key对应的缓存
snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
fileInputStream = (FileInputStream) snapShot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
// 将缓存数据解析成Bitmap对象
Bitmap bitmap =
if (fileDescriptor != null) {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
if (bitmap != null) {
// 将Bitmap对象添加到内存缓存当中
addBitmapToMemoryCache(params[0], bitmap);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileDescriptor == null && fileInputStream != null) {
fileInputStream.close();
} catch (IOException e) {
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
// 根据Tag找到相应的ImageView控件,将下载好的图片显示出来。
ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
taskCollection.remove(this);
* 建立HTTP请求,并获取Bitmap对象。
* @param imageUrl
图片的URL地址
* @return 解析后的Bitmap对象
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection =
BufferedOutputStream out =
BufferedInputStream in =
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
while ((b = in.read()) != -1) {
out.write(b);
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
if (out != null) {
out.close();
if (in != null) {
in.close();
} catch (final IOException e) {
e.printStackTrace();
代码有点长,我们一点点进行分析。首先在PhotoWallAdapter的构造函数中,我们初始化了LruCache类,并设置了内存缓存容量为程序最大可用内存的1/8,紧接着调用了DiskLruCache的open()方法来创建实例,并设置了硬盘缓存容量为10M,这样我们就把LruCache和DiskLruCache的初始化工作完成了。
接着在getView()方法中,我们为每个ImageView设置了一个唯一的Tag,这个Tag的作用是为了后面能够准确地找回这个ImageView,不然异步加载图片会出现乱序的情况。然后在getView()方法的最后调用了loadBitmaps()方法,加载图片的具体逻辑也就是在这里执行的了。
进入到loadBitmaps()方法中可以看到,实现是调用了getBitmapFromMemoryCache()方法来从内存中获取缓存,如果获取到了则直接调用ImageView的setImageBitmap()方法将图片显示到界面上。如果内存中没有获取到,则开启一个BitmapWorkerTask任务来去异步加载图片。
那么在BitmapWorkerTask的doInBackground()方法中,我们就灵活运用了上篇文章中学习的DiskLruCache的各种用法。首先根据图片的URL生成对应的MD5 key,然后调用DiskLruCache的get()方法来获取硬盘缓存,如果没有获取到的话则从网络上请求图片并写入硬盘缓存,接着将Bitmap对象解析出来并添加到内存缓存当中,最后将这个Bitmap对象显示到界面上,这样一个完整的流程就执行完了。
那么我们再来分析一下上述流程,每次加载图片的时候都优先去内存缓存当中读取,当读取不到的时候则回去硬盘缓存中读取,而如果硬盘缓存仍然读取不到的话,就从网络上请求原始数据。不管是从硬盘缓存还是从网络获取,读取到了数据之后都应该添加到内存缓存当中,这样的话我们下次再去读取图片的时候就能迅速从内存当中读取到,而如果该图片从内存中被移除了的话,那就重复再执行一遍上述流程就可以了。
这样我们就把LruCache和DiskLruCache完美结合到一起了。接下来还需要编写MainActivity的代码,非常简单,如下所示:
public class MainActivity extends Activity {
* 用于展示照片墙的GridView
private GridView mPhotoW
* GridView的适配器
private PhotoWallAdapter mA
private int mImageThumbS
private int mImageThumbS
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageThumbSize = getResources().getDimensionPixelSize(
R.dimen.image_thumbnail_size);
mImageThumbSpacing = getResources().getDimensionPixelSize(
R.dimen.image_thumbnail_spacing);
mPhotoWall = (GridView) findViewById(R.id.photo_wall);
mAdapter = new PhotoWallAdapter(this, 0, Images.imageThumbUrls,
mPhotoWall);
mPhotoWall.setAdapter(mAdapter);
mPhotoWall.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
final int numColumns = (int) Math.floor(mPhotoWall
.getWidth()
/ (mImageThumbSize + mImageThumbSpacing));
if (numColumns & 0) {
int columnWidth = (mPhotoWall.getWidth() / numColumns)
- mImageThumbS
mAdapter.setItemHeight(columnWidth);
mPhotoWall.getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
protected void onPause() {
super.onPause();
mAdapter.fluchCache();
protected void onDestroy() {
super.onDestroy();
// 退出程序时结束所有的下载任务
mAdapter.cancelAllTasks();
上述代码中,我们通过getViewTreeObserver()的方式监听View的布局事件,当布局完成以后,我们重新修改一下GridView中子View的高度,以保证子View的宽度和高度可以保持一致。
到这里还没有结束,最后还需要配置一下AndroidManifest.xml文件,并加入相应的权限,如下所示:
&manifest xmlns:android=&/apk/res/android&
package=&com.example.photoswalldemo&
android:versionCode=&1&
android:versionName=&1.0& &
android:minSdkVersion=&14&
android:targetSdkVersion=&17& /&
&uses-permission android:name=&android.permission.INTERNET& /&
&uses-permission android:name=&android.permission.WRITE_EXTERNAL_STORAGE& /&
&application
android:allowBackup=&true&
android:icon=&@drawable/ic_launcher&
android:label=&@string/app_name&
android:theme=&@style/AppTheme& &
android:name=&com.example.photoswalldemo.MainActivity&
android:label=&@string/app_name& &
&intent-filter&
&action android:name=&android.intent.action.MAIN& /&
&category android:name=&android.intent.category.LAUNCHER& /&
&/intent-filter&
&/activity&
&/application&
&/manifest&
好了,全部代码都在这儿了,让我们来运行一下吧,效果如下图所示:
第一次从网络上请求图片的时候有点慢,但之后加载图片就会非常快了,滑动起来也很流畅。
那么我们最后再检查一下这些图片是不是已经正确缓存在指定地址了,进入 /sdcard/Android/data/&application package&/cache/thumb 这个路径,如下图所示:
可以看到,每张图片的缓存以及journal文件都在这里了,说明我们的硬盘缓存已经成功了。
第二部分,LruCache和DiskLruCache的原理实际上是LinkedHashMap和File,
下面就来看看使用LinkedHashMap和File的实现:
网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决。下面提出一些优化:
1、采用线程池
2、内存缓存+文件缓存
3、内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4
4、对下载的图片进行按比例缩放,以减少内存的消耗
具体的代码里面说明。先放上内存缓存类的代码MemoryCache.java:
public class MemoryCache {
private static final String TAG = &MemoryCache&;
// 放入缓存时是个同步操作
// LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU
// 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
private Map&String, Bitmap& cache = Collections
.synchronizedMap(new LinkedHashMap&String, Bitmap&(10, 1.5f, true));
// 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存
private long size = 0;// current allocated size
// 缓存只能占用的最大堆内存
private long limit = 1000000;// max memory in bytes
public MemoryCache() {
// use 25% of available heap size
setLimit(Runtime.getRuntime().maxMemory() / 4);
public void setLimit(long new_limit) {
limit = new_
Log.i(TAG, &MemoryCache will use up to & + limit / 1024. / 1024. + &MB&);
public Bitmap get(String id) {
if (!cache.containsKey(id))
return cache.get(id);
} catch (NullPointerException ex) {
public void put(String id, Bitmap bitmap) {
if (cache.containsKey(id))
size -= getSizeInBytes(cache.get(id));
cache.put(id, bitmap);
size += getSizeInBytes(bitmap);
checkSize();
} catch (Throwable th) {
th.printStackTrace();
* 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
private void checkSize() {
Log.i(TAG, &cache size=& + size + & length=& + cache.size());
if (size & limit) {
// 先遍历最近最少使用的元素
Iterator&Entry&String, Bitmap&& iter = cache.entrySet().iterator();
while (iter.hasNext()) {
Entry&String, Bitmap& entry = iter.next();
size -= getSizeInBytes(entry.getValue());
iter.remove();
if (size &= limit)
Log.i(TAG, &Clean cache. New size & + cache.size());
public void clear() {
cache.clear();
* 图片占用的内存
* @param bitmap
long getSizeInBytes(Bitmap bitmap) {
if (bitmap == null)
return bitmap.getRowBytes() * bitmap.getHeight();
也可以使用SoftReference,代码会简单很多,但是推荐上面的方法。因为上面的方法更有效率,而SoftReference要依靠系统回收,并且对文件大小没有节制。
public class MemoryCache {
private Map&String, SoftReference&Bitmap&& cache = Collections
.synchronizedMap(new HashMap&String, SoftReference&Bitmap&&());
public Bitmap get(String id) {
if (!cache.containsKey(id))
SoftReference&Bitmap& ref = cache.get(id);
return ref.get();
public void put(String id, Bitmap bitmap) {
cache.put(id, new SoftReference&Bitmap&(bitmap));
public void clear() {
cache.clear();
下面是文件缓存类的代码FileCache.java:下面是文件缓存类的代码FileCache.java:
public class FileCache {
private File cacheD
public FileCache(Context context) {
// 如果有SD卡则在SD卡中建一个LazyList的目录存放缓存的图片
// 没有SD卡就放在系统的缓存目录中
if (android.os.Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED))
cacheDir = new File(
android.os.Environment.getExternalStorageDirectory(),
&LazyList&);
cacheDir = context.getCacheDir();
if (!cacheDir.exists())
cacheDir.mkdirs();
public File getFile(String url) {
// 将url的hashCode作为缓存的文件名
String filename = String.valueOf(url.hashCode());
// Another possible solution
// String filename = URLEncoder.encode(url);
File f = new File(cacheDir, filename);
public void clear() {
File[] files = cacheDir.listFiles();
if (files == null)
for (File f : files)
f.delete();
最后最重要的加载图片的类,ImageLoader.java:public class ImageLoader {
MemoryCache memoryCache = new MemoryCache();
FileCache fileC
private Map&ImageView, String& imageViews = Collections
.synchronizedMap(new WeakHashMap&ImageView, String&());
ExecutorService executorS
public ImageLoader(Context context) {
fileCache = new FileCache(context);
executorService = Executors.newFixedThreadPool(5);
// 当进入listview时默认的图片,可换成你自己的默认图片
final int stub_id = R.drawable.
// 最主要的方法
public void DisplayImage(String url, ImageView imageView) {
imageViews.put(imageView, url);
// 先从内存缓存中查找
Bitmap bitmap = memoryCache.get(url);
if (bitmap != null)
imageView.setImageBitmap(bitmap);
// 若没有的话则开启新线程加载图片
queuePhoto(url, imageView);
imageView.setImageResource(stub_id);
private void queuePhoto(String url, ImageView imageView) {
PhotoToLoad p = new PhotoToLoad(url, imageView);
executorService.submit(new PhotosLoader(p));
private Bitmap getBitmap(String url) {
File f = fileCache.getFile(url);
// 先从文件缓存中查找是否有
Bitmap b = decodeFile(f);
if (b != null)
// 最后从指定的url中下载图片
Bitmap bitmap =
URL imageUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) imageUrl
.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
InputStream is = conn.getInputStream();
OutputStream os = new FileOutputStream(f);
CopyStream(is, os);
os.close();
bitmap = decodeFile(f);
} catch (Exception ex) {
ex.printStackTrace();
// decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的
private Bitmap decodeFile(File f) {
// decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds =
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE = 70;
int width_tmp = o.outWidth, height_tmp = o.outH
int scale = 1;
while (true) {
if (width_tmp / 2 & REQUIRED_SIZE
|| height_tmp / 2 & REQUIRED_SIZE)
width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
// decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize =
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {
// Task for the queue
private class PhotoToLoad {
public ImageView imageV
public PhotoToLoad(String u, ImageView i) {
imageView =
class PhotosLoader implements Runnable {
PhotoToLoad photoToL
PhotosLoader(PhotoToLoad photoToLoad) {
this.photoToLoad = photoToL
public void run() {
if (imageViewReused(photoToLoad))
Bitmap bmp = getBitmap(photoToLoad.url);
memoryCache.put(photoToLoad.url, bmp);
if (imageViewReused(photoToLoad))
BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
// 更新的操作放在UI线程中
Activity a = (Activity) photoToLoad.imageView.getContext();
a.runOnUiThread(bd);
* 防止图片错位
* @param photoToLoad
boolean imageViewReused(PhotoToLoad photoToLoad) {
String tag = imageViews.get(photoToLoad.imageView);
if (tag == null || !tag.equals(photoToLoad.url))
// 用于在UI线程中更新界面
class BitmapDisplayer implements Runnable {
PhotoToLoad photoToL
public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
photoToLoad =
public void run() {
if (imageViewReused(photoToLoad))
if (bitmap != null)
photoToLoad.imageView.setImageBitmap(bitmap);
photoToLoad.imageView.setImageResource(stub_id);
public void clearCache() {
memoryCache.clear();
fileCache.clear();
public static void CopyStream(InputStream is, OutputStream os) {
final int buffer_size = 1024;
byte[] bytes = new byte[buffer_size];
for (;;) {
int count = is.read(bytes, 0, buffer_size);
if (count == -1)
os.write(bytes, 0, count);
} catch (Exception ex) {
主要流程是先从内存缓存中查找,若没有再开线程,从文件缓存中查找都没有则从指定的url中查找,并对bitmap进行处理,最后通过下面方法对UI进行更新操作:
a.runOnUiThread(...);
在你的程序中的基本用法:
ImageLoader imageLoader=new ImageLoader(context);
imageLoader.DisplayImage(url, imageView);
比如你的放在你的ListView的adapter的getView()方法中,当然也适用于GridView。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:336363次
积分:3489
积分:3489
排名:第6788名
原创:54篇
转载:38篇
评论:49条
(2)(17)(12)(16)(8)(1)(3)(2)(1)(2)(3)(5)(7)(10)

我要回帖

更多关于 disklrucache.java 的文章

 

随机推荐