1.什么是 OOM?为什么会引起 OOM?
答:Out Of Memory(内存溢出),我们都知道 Android 系统会为每个 APP 分配一个独立的工作空间,或者说分配一个单独的 Dalvik 虚拟机,这样每个 APP 都可以独立运行而不相互影响!而 Android 对于每个 Dalvik 虚拟机都会有一个最大内存限制,如果当前占用的内存加上我们申请的内存资源超过了这个限制,系统就会抛出 OOM 错误!另外,这里别和 RAM 混淆了,即时当前 RAM 中剩余的内存有 1G 多,但是 OOM 还是会发生!别把 RAM(物理内存)和 OOM 扯到一起!另外 RAM 不足的话,就是杀应用了,而不是仅仅是 OOM 了!而这个 Dalvik 中的最大内存标准,不同的机型是不一样的,可以调用:
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
Log.e("HEHE","最大内存:" + activityManager.getMemoryClass());
复制代码
获得正常的最大内存标准,又或者直接在命令行键入:
adb shell getprop | grep dalvik.vm.heapgrowthlimit
复制代码
你也可以打开系统源码/system/build.prop 文件,看下文件中这一部分的信息得出:
dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=192m
dalvik.vm.heapsize=512m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=2m
dalvik.vm.heapmaxfree=8m
复制代码
我们关注的地方有三个:heapstartsize 堆内存的初始大小,heapgrowthlimit 标准的应用的最大堆内存大小,heapsize 则是设置了使用 android:largeHeap 的应用的最大堆内存大小!
我这里试了下手头几个机型的正常最大内存分配标准:
你也可以试试自己手头的机子~
好啦,不扯了,关于 OOM 问题的产生,就扯到这里,再扯就到内存管理那一块了,可是个大块头,现在还啃不动...下面我们来看下避免 Bitmap OOM 的一些技巧吧!
2.避免 Bitmap 引起的 OOM 技巧小结
1)采用低内存占用量的编码方式
上一节说了 BitmapFactory.Options 这个类,我们可以设置下其中的 inPreferredConfig 属性,默认是 Bitmap.Config.ARGB_8888,我们可以修改成 Bitmap.Config.ARGB_4444Bitmap.Config ARGB_4444:每个像素占四位,即 A=4,R=4,G=4,B=4,那么一个像素点占 4+4+4+4=16 位 Bitmap.Config ARGB_8888:每个像素占八位,即 A=8,R=8,G=8,B=8,那么一个像素点占 8+8+8+8=32 位默认使用 ARGB_8888,即一个像素占 4 个字节!
2)图片压缩
同样是 BitmapFactory.Options,我们通过 inSampleSize 设置缩放倍数,比如写 2,即长宽变为原来的 1/2,图片就是原来的 1/4,如果不进行缩放的话设置为 1 即可!但是不能一味的压缩,毕竟这个值太小的话,图片会很模糊,而且要避免图片的拉伸变形,所以需要我们在程序中动态的计算,这个 inSampleSize 的合适值,而 Options 中又有这样一个方法:inJustDecodeBounds,将该参数设置为 true 后,decodeFiel 并不会分配内存空间,但是可以计算出原始图片的长宽,调用 options.outWidth/outHeight 获取出图片的宽高,然后通过一定的算法,即可得到适合的 inSampleSize,这里感谢街神提供的代码——摘自鸿洋 blog!
public static int caculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int width = options.outWidth;
int height = options.outHeight;
int inSampleSize = 1;
if (width > reqWidth || height > reqHeight) {
int widthRadio = Math.round(width * 1.0f / reqWidth);
int heightRadio = Math.round(height * 1.0f / reqHeight);
inSampleSize = Math.max(widthRadio, heightRadio);
}
return inSampleSize;
}
复制代码
然后使用下上述的方法即可:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 设置了此属性一定要记得将值设置为false
Bitmap bitmap = null;
bitmap = BitmapFactory.decodeFile(url, options);
options.inSampleSize = computeSampleSize(options,128,128);
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
/* 下面两个字段需要组合使用 */
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
try {
bitmap = BitmapFactory.decodeFile(url, options);
} catch (OutOfMemoryError e) {
Log.e(TAG, "OutOfMemoryError");
}
复制代码
3.及时回收图像
如果引用了大量的 Bitmap 对象,而应用又不需要同时显示所有图片。可以将暂时不用到的 Bitmap 对象及时回收掉。对于一些明确知道图片使用情况的场景可以主动 recycle 回收,比如引导页的图片,使用完就 recycle,帧动画,加载一张,画一张,释放一张!使用时加载,不显示时直接置 null 或 recycle!比如:imageView.setImageResource(0); 不过某些情况下会出现特定图片反复加载,释放,再加载等,低效率的事情...
4.其他方法
下面这些方法,我并没有用过,大家可以自行查阅相关资料:
1.简单通过 SoftReference 引用方式管理图片资源
建个 SoftReference 的 hashmap 使用图片时先查询这个 hashmap 是否有 softreference, softreference 里的图片是否为空,如果为空就加载图片到 softreference 并加入 hashmap。无需再代码里显式的处理图片的回收与释放,gc 会自动处理资源的释放。这种方式处理起来简单实用,能一定程度上避免前一种方法反复加载释放的低效率。但还不够优化。
示例代码:
private Map<String, SoftReference<Bitmap>> imageMap
= new HashMap<String, SoftReference<Bitmap>>();
public Bitmap loadBitmap(final String imageUrl,final ImageCallBack imageCallBack) {
SoftReference<Bitmap> reference = imageMap.get(imageUrl);
if(reference != null) {
if(reference.get() != null) {
return reference.get();
}
}
final Handler handler = new Handler() {
public void handleMessage(final android.os.Message msg) {
//加入到缓存中
Bitmap bitmap = (Bitmap)msg.obj;
imageMap.put(imageUrl, new SoftReference<Bitmap>(bitmap));
if(imageCallBack != null) {
imageCallBack.getBitmap(bitmap);
}
}
};
new Thread(){
public void run() {
Message message = handler.obtainMessage();
message.obj = downloadBitmap(imageUrl);
handler.sendMessage(message);
}
}.start();
return null ;
}
// 从网上下载图片
private Bitmap downloadBitmap (String imageUrl) {
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream(new URL(imageUrl).openStream());
return bitmap ;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public interface ImageCallBack{
void getBitmap(Bitmap bitmap);
}
复制代码
2.LruCache + sd 的缓存方式
Android 3.1版本起,官方还提供了LruCache来进行cache处理,当存储Image的大小大于LruCache 设定的值,那么近期使用次数最少的图片就会被回收掉,系统会自动释放内存!
复制代码
使用示例:
步骤:
1)要先设置缓存图片的内存大小,我这里设置为手机内存的 1/8,手机内存的获取方式:int MAXMEMONRY = (int) (Runtime.getRuntime() .maxMemory() / 1024);
2)LruCache 里面的键值对分别是 URL 和对应的图片
3)重写了一个叫做 sizeOf 的方法,返回的是图片数量。
private LruCache<String, Bitmap> mMemoryCache;
private LruCacheUtils() {
if (mMemoryCache == null)
mMemoryCache = new LruCache<String, Bitmap>(
MAXMEMONRY / 8) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
@Override
protected void entryRemoved(boolean evicted, String key,
Bitmap oldValue, Bitmap newValue) {
Log.v("tag", "hard cache is full , push to soft cache");
}
};
}
复制代码
4)下面的方法分别是清空缓存、添加图片到缓存、从缓存中取得图片、从缓存中移除。
移除和清除缓存是必须要做的事,因为图片缓存处理不当就会报内存溢出,所以一定要引起注意。
public void clearCache() {
if (mMemoryCache != null) {
if (mMemoryCache.size() > 0) {
Log.d("CacheUtils",
"mMemoryCache.size() " + mMemoryCache.size());
mMemoryCache.evictAll();
Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size());
}
mMemoryCache = null;
}
}
public synchronized void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (mMemoryCache.get(key) == null) {
if (key != null && bitmap != null)
mMemoryCache.put(key, bitmap);
} else
Log.w(TAG, "the res is aready exits");
}
public synchronized Bitmap getBitmapFromMemCache(String key) {
Bitmap bm = mMemoryCache.get(key);
if (key != null) {
return bm;
}
return null;
}
/**
* 移除缓存
*
* @param key
*/
public synchronized void removeImageCache(String key) {
if (key != null) {
if (mMemoryCache != null) {
Bitmap bm = mMemoryCache.remove(key);
if (bm != null)
bm.recycle();
}
}
}
复制代码
评论