原文出处: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, 有以下多个因素应该放入考虑范围内,例如:
public class BitMapUtils {
private LruCache lruCache;
private Context mContext;
public BitMapUtils(Context context) {
this.mContext = context;
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int mCacheSize = maxMemory / 1024;
lruCache = new LruCache(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{
@Override
protected Bitmap doInBackground(Integer... integers) {
Bitmap bitmap = decodeSampleBitmapFromResource(mContext.getResources(), integers[0], 100, 100);
addBitmapToMemoryCache(String.valueOf(integers[0]),bitmap);
return bitmap;
}
}
public class PhotoWallAdapter extends ArrayAdapter implements OnScrollListener {
/**
* 记录所有正在下载或等待下载的任务。
*/
private Set taskCollection;
/**
* 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
*/
private LruCache 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();
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
mMemoryCache = new LruCache(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 {
/**
* 图片的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;
}
}
}