1.意义:加快读取速度,减少流量的消耗,减少崩溃的次数
2.Android应用中的UI现成5秒没有相应的话就会强制抛出异常,俗称ANR(Appliction Not Responce),对于获取远程的资源,这里特指的是从服务器获取的数据譬如图片等等,这种异常将会更加容易被抛出来,所以在Android 4.0 里面将限制了网络的访问,不允许将网络的访问放在主线程,低于4.0的版本就不会收到限制,这个是在测试的时候发现的,Android中提供两个方法来做这件事情:
启动一个心的现成来获取资源,完成后通过handler机制发送消息,同时在handler的接收端更新主线程,从而达到异步线程获取图片,接收端定义handler变量,同事复写handlMessage(Message msg)方法
本地缓存
对图片来说,你不可能让应用每次获取的时候都重新到远程服务器去下载,特别是显示ListView中的图片的时候,滑动的速度变得很快,这样将会造成ANR,即使图片比较小,但是图片还没来得及释放的话,累计的图片将会占用比较大的内存,但是又不能将所有的图片资源在获取之后放在内存中,使用弱引用保存对象的方法保存,因为图片的资源往往很占内存也比较容易造成ANR,那么如果下载下来的图片保存的SdCard中,下次直接从SDcard上去获取的话,是比较靠谱的缓存方法,采用LRU等一些算法可以保证sdcard被占用的空间的一小部分,这样即保证了图片的加载,节省了从远程获取的图片流量,又使Sdcard的空间只占用了一笑部分,另外一中方法是设置LRU规则跟过期的时间
代码的流程如下:
下载图片--->判断Sdcard上的空间--->判断开辟的10Mde空间--->保存图片--->过期策略
2.1在Sdcard上开辟一定的空间,需要先判断Sdcard上剩余的空间是否足够,如果足够的话,就可以开辟空间,例如开辟10M的内存空间用来保存图片
2.2当需要获取图片的时候,就先从Sdcard上的目录中去找,如果找的到的话,使用该图片,并且更新图片最后被使用的时间,如果找不到,通过URL去DownLoad
2.3去服务器下载图片,如果图片下载成功了,放入SDcard,并使用,如果失败了,应该有重试的机制重新下载,譬如三次
2.4下载成功后保存到Sdcard上需要判断10M的空间是否已经用完,如果没用完就保存,如果已经用完空间,就根据LRU规则删除一些最近没有被用户用到的资源
保存图片到SD的代码:
private void saveBmpToSd(Bitmap bm, String url) {
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 = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.JPEG, 100, 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() {
// TODO Auto-generated method stub
StatFs stat = new StatFs(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
*/
private void removeCache(String dirPath) {
File file = new File(dirPath);
File[] files = file.listFiles();
if (files == null) {
return;
}
int dirSize = 0;
for (int i = 0; i < files.length; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
dirSize += files[i].length();
}
}
if (dirSize > CACHE_SIZE * MB
|| FREE_SD_SPACE_NEEED_TO_CACHE > freeSpaceOnSd()) {
int removeFactor = (int) ((0.4 * files.length) + 10);
Arrays.sort(files, new FileLastModifSort());
Log.i(TAG, "Clear some expiredcache files");
for (int i = 0; i < removeFactor; i++) {
if (files[i].getName().contains(WHOLESALE_CONV)) {
files[i].delete();
}
}
}
}
/**
* 删除过期文件
* @param dirPath
* @param fileName
*/
private void removeExpiredCache(String dirPath, String fileName) {
File file = new File(dirPath, fileName);
if (System.currentTimeMillis() - file.lastModified() > M_TIME_DIFF) {
Log.i(TAG, "Clear some expiredcache files");
file.delete();
}
}
文件使用时间排序:
/**
* 根据文件的最后修改时间进行排序
* @author huanglong
*
*/
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;
}
}
}
内存保存:
在内存中保存的话,只能保存一定的量,而不能一直往里面放,需要设置数据的过期时间,LRU等算法,这里有一个方法是把常用的数据放到一个缓存中(A),不常用的放在另外一个缓存中(B),当要获取数据时候先从A中去获取,如果A中存在在去B中获取,B中的数据主要是A中LUR出来的数据,这里的内存回收主要是针对B,从而保持A中的数据可以有效的被命中。
定义A缓存:
// 定义A缓存
private final HashMap<String, Bitmap> mHardBItmapCache = new LinkedHashMap<String, Bitmap>(
HARD_CACHE_CAPACITY / 2, 0.75f, true) {
@Override
protected boolean removeEldestEntry(
java.util.Map.Entry<String, Bitmap> eldest) {
// TODO Auto-generated method stub
if (size() > HARD_CACHE_CAPACITY) {
// 保证map的size大于30时候,把最不常用的key放到mSoftBitmapCache中,从而保证mHardBitmapCache的效率
mSoftBitmapCache.put(eldest.getKey(),
new SoftReference<Bitmap>(eldest.getValue()));
return true;
} else {
return false;
}
}
};
定义B缓存:
// 定义B缓存
// 当mHardBitmapCache的key大于30的时候,会根据LRU算法把最近没有使用的Key放入到缓存中
// Bitmap 使用了SoftReference 当内存不足的时候,此时cache中的bitmap会被垃圾回收掉
private final static ConcurrentHashMap<String, SoftReference<Bitmap>> mSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(
HARD_CACHE_CAPACITY / 2);
从缓存中获取数据:
/**
* 从缓存中获得数据
*
* @param url
* @return
*/
private Bitmap getBitmapFromeCache(String url) {
// 先从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;
}
如果缓存不存在,那么就只能去服务器下载:
// 如果缓存中不存在那么就只能去服务器下载
class ImageDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private static final int IO_BUFFER_SIZE = 4 * 1024;
private String url;
private final WeakReference<ImageView> imageViewReferrReference;
private ImageDownloaderTask() {
imageViewReferrReference = new WeakReference<ImageView>(imageView);
}
@Override
protected Bitmap doInBackground(String... params) {
// TODO Auto-generated method stub
final AndroidHttpClient client = AndroidHttpClient
.newInstance("Android");
url = params[0];
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.v(TAG, "从" + url + "中下载图片是出错! 错误码:" + statusCode);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = entity.getContent();
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
outputStream = new BufferedOutputStream(dataStream,
IO_BUFFER_SIZE);
copy(inputStream, outputStream);
outputStream.flush();
final byte[] data = dataStream.toByteArray();
final Bitmap bitma = BitmapFactory.decodeByteArray(
data, 0, data.length);
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
entity.consumeContent();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
getRequest.abort();
Log.w(TAG, "Incorrect URL :" + url);
e.printStackTrace();
} finally {
if (client != null) {
client.close();
}
}
return null;
}
}
这是两种做法,还有一些应用在下载的时候使用了线程池和消息队列MQ,对于图片的下载效果会更好一些
http://mobile.51cto.com/android-288600.htm