原文出處:http://blog.csdn.net/guolin_blog/article/details/9316683
大家都知道,如果加載的圖片過大,就是出過OOM(內存溢出異常)
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.d("TAG", "Max memory is " + maxMemory + "KB");
當我們加載大圖片的時候應該將圖片壓縮。
BitmapFactory 提供了多種創建Bitmap的方法,這些方法都會爲bitMap分配內存,這樣很容易出現OOM,不過BitMapFactory提供了一個可選的BtiMapFactory.Options參數,將這個參數的inJustDecodeBounds屬性設置爲true,就可以禁止爲BitMap非配內存,返回值不會是BitMap,而是null,雖然是空但是可以獲取bitmap的outWidth,outHeight,outMimeType.
設置BitMapFactory.Options中的inSampleSize的值可以對圖片進行壓縮。
下面的代碼通過傳入的的值,對大圖片的進行縮放
首先獲取縮放值:
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源圖片的高度和寬度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 計算出實際寬高和目標寬高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 選擇寬和高中最小的比率作爲inSampleSize的值,這樣可以保證最終圖片的寬和高
// 一定都會大於等於目標的寬和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析將inJustDecodeBounds設置爲true,來獲取圖片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 調用上面定義的方法計算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用獲取到的inSampleSize值再次解析圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
使用緩存技術:
加載一張大圖片是一個簡單的事,當使用ListView加載一大堆圖片,就要使用到緩存的技術了,不然也會出現OOM。
爲了保證內存始終維持在一個合理的範圍,將移出屏幕的圖片進行回收,此時垃圾回收器認爲你不再持有這些圖片的引用,從而對這些圖片進行GC操作,但是當我們重新滑到上次的位置時,如果再去重新加載圖片無疑性能是非常差的。
這個時候我們內存緩存,它可以讓組件快速的重新加載和處理圖片,內存緩存技術可以對那些大量佔用內存的圖片提供快速訪問的方法。核心類就是LruCache。這個類非常適合用來緩存圖片,它的主要算法是把最近使用的對象的強引用添加到LinkedHashMap中,並且把最近最少使用的對象從內存中刪除。
在2.3之前,我們會用軟引用或者弱引用來實現內存緩存,2.3之後垃圾回收期更傾向於回收軟引用或弱引用的對象。在3.0之後,圖片的數據會存儲在本地的內存中,因而無法用一種可預見的方式將其釋放。
爲了能夠選擇一個適合的緩存大小給lruCache,有多個元素需要考慮。
爲了能夠選擇一個合適的緩存大小給LruCache, 有以下多個因素應該放入考慮範圍內,例如:
- 你的設備可以爲每個應用程序分配多大的內存?
- 設備屏幕上一次最多能顯示多少張圖片?有多少圖片需要進行預加載,因爲有可能很快也會顯示在屏幕上?
- 你的設備的屏幕大小和分辨率分別是多少?一個超高分辨率的設備(例如 Galaxy Nexus) 比起一個較低分辨率的設備(例如 Nexus S),在持有相同數量圖片的時候,需要更大的緩存空間。
- 圖片的尺寸和大小,還有每張圖片會佔據多少內存空間。
- 圖片被訪問的頻率有多高?會不會有一些圖片的訪問頻率比其它圖片要高?如果有的話,你也許應該讓一些圖片常駐在內存當中,或者使用多個LruCache 對象來區分不同組的圖片。
- 你能維持好數量和質量之間的平衡嗎?有些時候,存儲多個低像素的圖片,而在後臺去開線程加載高像素的圖片會更加的有效。
public class BitMapUtils { private LruCache<String, Bitmap> lruCache; private Context mContext; public BitMapUtils(Context context) { this.mContext = context; int maxMemory = (int) Runtime.getRuntime().maxMemory(); int mCacheSize = maxMemory / 1024; lruCache = new LruCache<String, Bitmap>(mCacheSize) { @Override protected int sizeOf(String key, Bitmap value) { //重寫次方法來衡量每張圖的大小,默認返回圖片的數量 return value.getByteCount() / 1024; } }; } public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (lruCache.get(key) == null) { lruCache.put(key, bitmap); } } public Bitmap getBitmapFromMemCache(String key) { return lruCache.get(key); } public void loadBitMap(int resId, ImageView imageView) { String imageKey = String.valueOf(resId); Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { imageView.setImageBitmap(bitmap); }else{ imageView.setImageResource(R.drawable.emotionstore_progresscancelbtn); BitMapWorkerTask bitMapWorkerTask = new BitMapWorkerTask(); bitMapWorkerTask.execute(resId); } } /** * 計算縮放值 * * @param options * @param reqWidth * @param reqHeight * @return */ private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { int height = options.outHeight; int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { int widthRatio = Math.round(width / reqWidth); int heightRatio = Math.round(height / reqHeight); inSampleSize = Math.min(widthRatio, heightRatio); } return inSampleSize; } /** * 加載大圖片 * @param res * @param resId * @param reqWidth * @param reqHeight * @return */ public static Bitmap decodeSampleBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); int inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inSampleSize = inSampleSize; options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } class BitMapWorkerTask extends AsyncTask<Integer,Void,Bitmap>{ @Override protected Bitmap doInBackground(Integer... integers) { Bitmap bitmap = decodeSampleBitmapFromResource(mContext.getResources(), integers[0], 100, 100); addBitmapToMemoryCache(String.valueOf(integers[0]),bitmap); return bitmap; } }
如果是ListView或者GridView列表加載圖片時- 創建一個Set集合
- 設置onScoll監聽
- 給ImageView設置 tag
public class PhotoWallAdapter extends ArrayAdapter<String> implements OnScrollListener {
/**
* 記錄所有正在下載或等待下載的任務。
*/
private Set<BitmapWorkerTask> taskCollection;
/**
* 圖片緩存技術的核心類,用於緩存所有下載好的圖片,在程序內存達到設定值時會將最少最近使用的圖片移除掉。
*/
private LruCache<String, Bitmap> mMemoryCache;
/**
* GridView的實例
*/
private GridView mPhotoWall;
/**
* 第一張可見圖片的下標
*/
private int mFirstVisibleItem;
/**
* 一屏有多少張圖片可見
*/
private int mVisibleItemCount;
/**
* 記錄是否剛打開程序,用於解決進入程序不滾動屏幕,不會下載圖片的問題。
*/
private boolean isFirstEnter = true;
public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects,
GridView photoWall) {
super(context, textViewResourceId, objects);
mPhotoWall = photoWall;
taskCollection = new HashSet<BitmapWorkerTask>();
// 獲取應用程序最大可用內存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 設置圖片緩存大小爲程序最大可用內存的1/8
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
mPhotoWall.setOnScrollListener(this);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final String url = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);
} else {
view = convertView;
}
final ImageView photo = (ImageView) view.findViewById(R.id.photo);
// 給ImageView設置一個Tag,保證異步加載圖片時不會亂序
photo.setTag(url);
setImageView(url, photo);
return view;
}
/**
* 給ImageView設置圖片。首先從LruCache中取出圖片的緩存,設置到ImageView上。如果LruCache中沒有該圖片的緩存,
* 就給ImageView設置一張默認圖片。
*
* @param imageUrl
* 圖片的URL地址,用於作爲LruCache的鍵。
* @param imageView
* 用於顯示圖片的控件。
*/
private void setImageView(String imageUrl, ImageView imageView) {
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.empty_photo);
}
}
/**
* 將一張圖片存儲到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);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 僅當GridView靜止時纔去下載圖片,GridView滑動時取消所有正在下載的任務
if (scrollState == SCROLL_STATE_IDLE) {
loadBitmaps(mFirstVisibleItem, mVisibleItemCount);
} else {
cancelAllTasks();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
// 下載的任務應該由onScrollStateChanged裏調用,但首次進入程序時onScrollStateChanged並不會調用,
// 因此在這裏爲首次進入程序開啓下載任務。
if (isFirstEnter && visibleItemCount > 0) {
loadBitmaps(firstVisibleItem, visibleItemCount);
isFirstEnter = false;
}
}
/**
* 加載Bitmap對象。此方法會在LruCache中檢查所有屏幕中可見的ImageView的Bitmap對象,
* 如果發現任何一個ImageView的Bitmap對象不在緩存中,就會開啓異步線程去下載圖片。
*
* @param firstVisibleItem
* 第一個可見的ImageView的下標
* @param visibleItemCount
* 屏幕中總共可見的元素數
*/
private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
try {
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
String imageUrl = Images.imageThumbUrls[i];
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (bitmap == null) {
BitmapWorkerTask task = new BitmapWorkerTask();
taskCollection.add(task);
task.execute(imageUrl);
} else {
ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(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);
}
}
}
/**
* 異步下載圖片的任務。
*
* @author guolin
*/
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
/**
* 圖片的URL地址
*/
private String imageUrl;
@Override
protected Bitmap doInBackground(String... params) {
imageUrl = params[0];
// 在後臺開始下載圖片
Bitmap bitmap = downloadBitmap(params[0]);
if (bitmap != null) {
// 圖片下載完成後緩存到LrcCache中
addBitmapToMemoryCache(params[0], bitmap);
}
return bitmap;
}
@Override
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 Bitmap downloadBitmap(String imageUrl) {
Bitmap bitmap = null;
HttpURLConnection con = null;
try {
URL url = new URL(imageUrl);
con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(5 * 1000);
con.setReadTimeout(10 * 1000);
bitmap = BitmapFactory.decodeStream(con.getInputStream());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (con != null) {
con.disconnect();
}
}
return bitmap;
}
}
}