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