1. 异步下载 / 本地缓存

异步下载

大家都知道,在Android应用中UI线程5秒没响应的话就会抛出无响应异常,对于远程获取大的资源来说,这种异常还是很容易就会抛出来的,那么怎么避免这种问题的产生。在android中提供两种方法来做这件事情:

  1. 启动一个新的线程来获取资源,完成后通过Handler机制发送消息,并在UI线程中处理消息,从而达到在异步线程中获取图片,然后通过Handler Message来更新UI线程的过程。
  2. 使用android中提供的AsyncTask来完成。

具体的做法这里就不介绍了,查下API就可以了,或者是google、baidu下。这里主要来说本地缓存。

本地缓存

对于图片资源来说,你不可能让应用每次获取的时候都重新到远程去下载(ListView),这样会浪费资源,但是你又不能让所有图片资源都放到内存中去(虽然这样加载会比较快),因为图片资源往往会占用很大的内存空间,容易导致OOM。那么如果下载下来的图片保存到SDCard中,下次直接从SDCard上去获取呢?这也是一种做法,我看了下,还是有不少应用采用这种方式的。采用LRU等一些算法可以保证sdcard被占用的空间只有一小部分,这样既保证了图片的加载、节省了流量、又使SDCard的空间只占用了一小部分。另外一种做法是资源直接保存在内存中,然后设置过期时间和LRU规则。

sdcard保存:

  1. 在sdcard上开辟一定的空间,需要先判断sdcard上剩余空间是否足够,如果足够的话就可以开辟一些空间,比如10M
  2. 当需要获取图片时,就先从sdcard上的目录中去找,如果找到的话,使用该图片,并更新图片最后被使用的时间。如果找不到,通过URL去download
  3. 去服务器端下载图片,如果下载成功了,放入到sdcard上,并使用,如果失败了,应该有重试机制。比如3次。
  4. 下载成功后保存到sdcard上,需要先判断10M空间是否已经用完,如果没有用完就保存,如果空间不足就根据LRU规则删除一些最近没有被用户的资源。

关键代码:

  • 保存图片到SD卡上
private void saveBmpToSd(Bitmap bm, Stringurl) {
if (bm == null) {
Log.w(TAG, " trying to savenull bitmap");
return;
}
//判断sdcard上的空间
if (FREE_SD_SPACE_NEEDED_TO_CACHE >freeSpaceOnSd()) {
Log.w(TAG, "Low free space onsd, do not cache");
return;
}
String filename =convertUrlToFileName(url);
String dir = getDirectory(filename);
File file = new File(dir +"/" + filename);
try {
file.createNewFile();
OutputStream outStream = newFileOutputStream(file);
bm.compress(Bitmap.CompressFormat.JPEG, , outStream);
outStream.flush();
outStream.close();
Log.i(TAG, "Image saved tosd");
} catch (FileNotFoundException e) {
Log.w(TAG,"FileNotFoundException");
} catch (IOException e) {
Log.w(TAG,"IOException");
}
}

计算sdcard上的空间:

    /**
* 计算sdcard上的剩余空间
* @return
*/
private int freeSpaceOnSd() {
StatFs stat = newStatFs(Environment.getExternalStorageDirectory() .getPath());
double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
return (int) sdFreeMB;
}

修改文件的最后修改时间

    /**
* 修改文件的最后修改时间
* @param dir
* @param fileName
*/
private void updateFileTime(String dir,String fileName) {
File file = new File(dir,fileName);
long newModifiedTime =System.currentTimeMillis();
file.setLastModified(newModifiedTime);
}

本地缓存优化

     /**
*计算存储目录下的文件大小,当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
* 那么删除40%最近没有被使用的文件
* @param dirPath
* @param filename
*/
private void removeCache(String dirPath) {
File dir = new File(dirPath);
File[] files = dir.listFiles();
if (files == null) {
return;
}
int dirSize = ;
for (int i = ; i < files.length;i++) {
if(files[i].getName().contains(WHOLESALE_CONV)) {
dirSize += files[i].length();
}
}
if (dirSize > CACHE_SIZE * MB ||FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
int removeFactor = (int) ((0.4 *files.length) + ); Arrays.sort(files, newFileLastModifSort()); Log.i(TAG, "Clear some expiredcache files "); for (int i = ; i <removeFactor; i++) { if(files[i].getName().contains(WHOLESALE_CONV)) { files[i].delete(); } } } }
/**
* 删除过期文件
* @param dirPath
* @param filename
*/
private void removeExpiredCache(StringdirPath, String filename) { File file = new File(dirPath,filename); if (System.currentTimeMillis() -file.lastModified() > mTimeDiff) { Log.i(TAG, "Clear some expiredcache files "); file.delete(); } }

文件使用时间排序

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

内存保存:

在内存中保存的话,只能保存一定的量,而不能一直往里面放,需要设置数据的过期时间、LRU等算法。这里有一个方法是把常用的数据放到一个缓存中(A),不常用的放到另外一个缓存中(B)。当要获取数据时先从A中去获取,如果A中不存在那么再去B中获取。B中的数据主要是A中LRU出来的数据,这里的内存回收主要针对B内存,从而保持A中的数据可以有效的被命中。

  1. 先定义A缓存:
private final HashMap<String, Bitmap>mHardBitmapCache = new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY/ , 0.75f, true) {
@Override
protected booleanremoveEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {
if (size() >HARD_CACHE_CAPACITY) {
//当map的size大于30时,把最近不常用的key放到mSoftBitmapCache中,从而保证mHardBitmapCache的效率
mSoftBitmapCache.put(eldest.getKey(), newSoftReference<Bitmap>(eldest.getValue()));
return true;
} else
return false;
}
};

2.再定于B缓存:

   /**
*当mHardBitmapCache的key大于30的时候,会根据LRU算法把最近没有被使用的key放入到这个缓存中。
*Bitmap使用了SoftReference,当内存空间不足时,此cache中的bitmap会被垃圾回收掉
*/
private final staticConcurrentHashMap<String, SoftReference<Bitmap>> mSoftBitmapCache =new ConcurrentHashMap<String,SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / );

3.从缓存中获取数据:

/**
* 从缓存中获取图片
*/
private Bitmap getBitmapFromCache(Stringurl) {
// 先从mHardBitmapCache缓存中获取
synchronized (mHardBitmapCache) {
final Bitmap bitmap =mHardBitmapCache.get(url);
if (bitmap != null) {
//如果找到的话,把元素移到linkedhashmap的最前面,从而保证在LRU算法中是最后被删除
mHardBitmapCache.remove(url);
mHardBitmapCache.put(url,bitmap);
return bitmap;
}
}
//如果mHardBitmapCache中找不到,到mSoftBitmapCache中找
SoftReference<Bitmap>bitmapReference = mSoftBitmapCache.get(url);
if (bitmapReference != null) {
final Bitmap bitmap =bitmapReference.get();
if (bitmap != null) {
return bitmap;
} else {
mSoftBitmapCache.remove(url);
}
}
return null;
}
  1. 如果缓存中不存在,那么就只能去服务器端去下载:
   /**
* 异步下载图片
*/
class ImageDownloaderTask extendsAsyncTask<String, Void, Bitmap> {
private static final int IO_BUFFER_SIZE= * ;
private String url;
private finalWeakReference<ImageView> imageViewReference;
public ImageDownloaderTask(ImageViewimageView) {
imageViewReference = newWeakReference<ImageView>(imageView);
} @Override
protected BitmapdoInBackground(String... params) {
final AndroidHttpClient client =AndroidHttpClient.newInstance("Android");
url = params[];
final HttpGet getRequest = newHttpGet(url);
try {
HttpResponse response =client.execute(getRequest);
final int statusCode =response.getStatusLine().getStatusCode();
if (statusCode !=HttpStatus.SC_OK) {
Log.w(TAG, "从" +url + "中下载图片时出错!,错误码:" + statusCode);
return null;
}
final HttpEntity entity =response.getEntity();
if (entity != null) {
InputStream inputStream =null;
OutputStream outputStream =null;
try {
inputStream =entity.getContent();
finalByteArrayOutputStream dataStream = new ByteArrayOutputStream();
outputStream = newBufferedOutputStream(dataStream, IO_BUFFER_SIZE);
copy(inputStream,outputStream);
outputStream.flush();
final byte[] data =dataStream.toByteArray();
final Bitmap bitmap =BitmapFactory.decodeByteArray(data, , data.length);
return bitmap;
} finally {
if (inputStream !=null) {
inputStream.close();
}
if (outputStream !=null) {
outputStream.close();
}
entity.consumeContent();
}
}
} catch (IOException e) {
getRequest.abort();
Log.w(TAG, "I/O errorwhile retrieving bitmap from " + url, e);
} catch (IllegalStateException e) {
getRequest.abort();
Log.w(TAG, "Incorrect URL:" + url);
} catch (Exception e) {
getRequest.abort();
Log.w(TAG, "Error whileretrieving bitmap from " + url, e);
} finally {
if (client != null) {
client.close();
}
}
return null;
}

这是两种做法,还有一些应用在下载的时候使用了线程池和消息队列MQ,对于图片下载的效率要更好一些。有兴趣的同学可以看下。

总结

  1. 对于远程图片等相对比较大的资源一定要在异步线程中去获取
  2. 本地做缓存

Android 图片缓存处理的更多相关文章

  1. Android图片缓存之Lru算法

    前言: 上篇我们总结了Bitmap的处理,同时对比了各种处理的效率以及对内存占用大小.我们得知一个应用如果使用大量图片就会导致OOM(out of memory),那该如何处理才能近可能的降低oom发 ...

  2. Android图片缓存之Glide进阶

    前言: 前面学习了Glide的简单使用(Android图片缓存之初识Glide),今天来学习一下Glide稍微复杂一点的使用. 图片缓存相关博客地址: Android图片缓存之Bitmap详解 And ...

  3. Android图片缓存之初识Glide

    前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...

  4. Android图片缓存之Bitmap详解

    前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap.BitmapFactory这两个类. 图片缓存相关博客地址: Android图片缓 ...

  5. 安卓高级 Android图片缓存之初识Glide

    前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...

  6. Android图片缓存框架Glide

    Android图片缓存框架Glide Glide是Google提供的一个组件.它具有获取.解码和展示视频剧照.图片.动画等功能.它提供了灵活的API,帮助开发者将Glide应用在几乎任何网络协议栈中. ...

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

    现在做的项目中,有用到一个开源的2D地图框架osmdroid,但是在项目中,使用还是有一些问题,例如,多个地图实例,会有独自的图片缓存,Activity onPause时,并不会释放图片缓存,如果多级 ...

  8. Android图片缓存之Glide进阶(四)

    前言: 前面学习了Glide的简单使用(http://www.cnblogs.com/whoislcj/p/5558168.html),今天来学习一下Glide稍微复杂一点的使用. GlideModu ...

  9. Android图片缓存之初识Glide(三)

    前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...

  10. Android图片缓存之Bitmap详解(一)

    前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap.BitmapFactory这两个类. Bitmap: Bitmap是Android ...

随机推荐

  1. 开源3D游戏引擎Irrlicht简介

    Irrlicht简介 Irrlicht在国内也被叫做"鬼火"引擎,是一款用C++编写的开放源代码的高性能游戏引擎.而且是跨平台的,具有很好的移植性,Irrlicht支持OpenGl ...

  2. java线程深入学习

    一.java中的线程是通过Thread类创建的, //下面是构造函数,一个共同的特点就是:都是调用init()进行创建的 public Thread() { init(null, null, &quo ...

  3. Swift学习笔记(2)--元组(Tuples)、Optional(可选值)、(Assertions)断言

    1.Tuples(元组) 元组是多个值组合而成的复合值.元组中的值可以是任意类型,而且每一个元素的类型可以是不同的. 1>定义:使用()包含所有元素,用逗号分开,也可以对每个元素做命名 let ...

  4. Java Web学习总结(9)——servlet和Jsp生命周期解读

    一.servlet的工作工程 Servlet是运行在Servlet容器(有时候也叫Servlet引擎,是web服务器和应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务,解码基于MIME的 ...

  5. qt程序实现打开文件夹

    QString path=QDir::currentPath();//获取程序当前目录 path.replace("/","\\");//将地址中的" ...

  6. 洛谷—— P1629 邮递员送信

    https://www.luogu.org/problem/show?pid=1629 题目描述 有一个邮递员要送东西,邮局在节点1.他总共要送N-1样东西,其目的地分别是2~N.由于这个城市的交通比 ...

  7. windows下搭建hadoop-2.6.0本地idea开发环境

    概述 本文记录windows下hadoop本地开发环境的搭建: OS:windows hadoop执行模式:独立模式 安装包结构: Hadoop-2.6.0-Windows.zip - cygwinI ...

  8. C++语言笔记系列之十三——派生类构造函数的调用

    1.派生类构造函数的调用 (1)一个基类的全部数据成员均被派生类继承.创建一个派生类对象时.系统在为派生类对象分配单元时一定要为其基类数据成员分配子空间. (2)一个派生类对象在创建时不仅要调用派生类 ...

  9. 深入解析开源项目之Universal-Image-Loader(二)硬盘---缓存篇

    文件命名: FileNameGenerator,HashCodeFileNameGenerator,Md5FileNameGenerator package com.nostra13.universa ...

  10. vim-缓存区中打开另外一个文件的方法

    现在有这么一种情况:    我现在在ubuntu用户根目录下--    我根目录下有一个文件夹blogs,这个文件夹下面有两个文件:text1,text2.    我现在从-目录下进行如下操作    ...