贴郭大神的链接,全是copy来的:http://blog.csdn.net/guolin_blog/article/details/9316683
一、查看每个应用程序最高可用内存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.d("TAG", "Max memory is " + maxMemory + "KB");
二、高效加载大图
BitmapFatory提供了多个解析方法(decodeByteArray、decodeFile、decodeResource、decodeStream等)用于创建Bitmap对象,我们应根据图片的来源选择合适的方法。调用这些方法时会分配内存,这是就容易出现OOM。解决方法:每种解析方法都提供了一个BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可让解析方法禁止为Bitmap分配内存,返回值不再是一个Bitmap对象,而是null。虽然Bitmap为null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧可让我们在加载图片前就可获取到图片的长宽和MIME类型,我们可据此对图片进行压缩。
1)设置BitmapFactory.Options的inJustDecodeBounds属性为true
2)调用BitmapFactory对应的decode方法解析图片获得宽和高
3)根据获得的宽和高和控件的宽和高来压缩图片。怎么压缩呢?通过BitmapFactory.Options中inSampleSize的值就可实现。比如有张2048*1536的图,将inSampleSize设为4,就可将这张图压缩成512*384像素。原本加载这张图需要13M内存,现在只需0.75M了(假设图片是ARGB_8888类型,即每个像素点占用四个字节)。
代码:
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);
}
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;
}
下面代码非常简洁的将任意一张图压缩成100*100的缩略图,并在ImageView上展示:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
三、压缩已经载入内存的图片
比如你要将相机的照片或者拍照的照片,显示到控件上,那你可能需要用到这种压缩方式。
核心思想:首先将图片转成一个输出流,并记录输出流的btye数组大小,通过调用bitmap对象的compress方法,对图片做一次压缩以及格式化,并将btye数组大小与期望压缩值的目标大小比对,得出压缩比率,并调用Bitmap的缩放方法,缩放计算出压缩比率,从而得到压缩后的方法。
/**
* 图片压缩方法:(使用compress的方法)
*
* @explain 如果bitmap本身的大小小于maxSize,则不作处理
* @param bitmap
* 要压缩的图片
* @param maxSize
* 压缩后的大小,单位kb
*/
public static void imageZoom(Bitmap bitmap, double maxSize) {
// 将bitmap放至数组中,意在获得bitmap的大小(与实际读取的原文件要大)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 格式、质量、输出流
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] b = baos.toByteArray();
// 将字节换成KB
double mid = b.length / 1024;
// 获取bitmap大小 是允许最大大小的多少倍
double i = mid / maxSize;
// 判断bitmap占用空间是否大于允许最大空间 如果大于则压缩 小于则不压缩
if (i > 1) {
// 缩放图片 此处用到平方根 将宽带和高度压缩掉对应的平方根倍
// (保持宽高不变,缩放后也达到了最大占用空间的大小)
bitmap = scale(bitmap, bitmap.getWidth() / Math.sqrt(i),
bitmap.getHeight() / Math.sqrt(i));
}
}
/***
* 图片的缩放方法
*
* @param src
* :源图片资源
* @param newWidth
* :缩放后宽度
* @param newHeight
* :缩放后高度
*/
public static Bitmap scale(Bitmap src, double newWidth, double newHeight) {
// 记录src的宽高
float width = src.getWidth();
float height = src.getHeight();
// 创建一个matrix容器
Matrix matrix = new Matrix();
// 计算缩放比例
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 开始缩放
matrix.postScale(scaleWidth, scaleHeight);
// 创建缩放后的图片
return Bitmap.createBitmap(src, 0, 0, (int) width, (int) height,
matrix, true);
}
四、使用图片缓存技术
内存缓存技术可以让组件快速的重新加载和处理图片。最核心的是LrcCache,这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
实现逻辑:1)定义LruCache对象(重写sizeOf方法),给其分配最大内存。2)给LruCache添加两个方法,一个存图片的方法,一个取图片的方法。3)给控件添加图片。此时就用到上一步的两个方法了,先用取得方法从LrcCache对象中取;如果没有再去下载,下载完了要保存到LruCache对象中。
贴代码:
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
}
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
// 在后台加载图片。
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100);
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
}
为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:
- 你的设备可以为每个应用程序分配多大的内存?
- 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
- 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
- 图片的尺寸和大小,还有每张图片会占据多少内存空间。
- 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
- 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。
并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。