Android Bitmap OOM 问题
OOM
就是 Out Of Memory (内存溢出)
Dalvik 最大内存
Android 系统会为每个 APP 分配一个独立的工作空间或者说分配一个单独的 Dalvik 虚拟机,让每个 APP 都可以独立运行而不相互影响
但 Android 对于每个 Dalvik
虚拟机都会有一个最大内存限制,如果当前占用的内存加上我们申请的内存资源超过了这个限制
,系统就会抛出 OOM 错误
当然,你不要将 OOM 和 RAM(物理内存) 混淆了,即使当前 RAM 中剩余的内存有 1G 多,但是 OOM 还是会发生
如果 RAM 不足的话,就是杀应用了,而不是仅仅是 OOM 了
Dalvik 的最大内存标准,不同的机型是不一样的,可以使用下面的代码查看
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); Log.e("oom","最大内存:" + 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
的应用的最大堆内存大小
避免 Bitmap 引起的 OOM
-
使用低内存占用量的编码方式
通过设置
BitmapFactory.Options
类中的属性inPreferredConfig
默认是
Bitmap.Config.ARGB_8888
,可以修改成Bitmap.Config.ARGB_4444
-
Bitmap.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位
-
-
压缩图片
通过设置
BitmapFactory.Options
类中的属性inSampleSize
inSampleSize
用于设置缩放倍数,比如写 2,即长宽变为原来的1/2
,图片就是原来的1/4
如果不进行缩放的话设置为
1
即可但是不能一味的压缩,毕竟这个值太小的话,图片会很模糊,而且要避免图片的拉伸变形,所以需要在程序中动态的计算,这个
inSampleSize
的合适值这时候就会用到
BitmapFactory.Options
类的一个属性inJustDecodeBounds
,设置为 true 后,decodeFiel
并不会分配内存空间,但是可以计算出原始图片的长宽调用
options.outWidth | outHeight
获取出图片的宽高,然后通过一定的算法,即可得到适合的 inSampleSizepublic 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"); }
-
及时回收图像
如果引用了大量的 Bitmap 对象,而应用又不需要同时显示所有图片可以将暂时不用到的 Bitmap 对象及时回收掉
对于一些明确知道图片使用情况的场景可以主动 recycle 回收,比如引导页的图片,使用 完就 recycle,帧动画,加载一张,画一张,释放一张
使用时加载,不显示时直接置 null 或 recycle 比如:imageView.setImageResource(0);
不过某些情况下会出现特定图片反复加载,释放,再加载等,低效率的事情.
-
简单通过 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); }
-
使用 LruCache + sd 的缓存方式
可以使用 Android 内置的 LruCache 类进行 cache 处理
当存储 Image 的大小大于 LruCache 设定的值,那么近期使用次数最少的图片就会被回收掉,系统会自动释放内存
-
先设置缓存图片的内存大小,比如设置为手机内存的 1/8,
手机内存大小的获取方式
int MAXMEMONRY = (int)(Runtime.getRuntime() .maxMemory()/1024);
-
LruCache 里面的键值对分别是 URL 和对应的图片
-
重写了
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"); } }; }
-
实现清空缓存、添加图片到缓存、从缓存中取得图片、从缓存中移除移除和清除缓存是必须要做的事,因为图片缓存处理不当就会报内存溢出,所以一定要引起注意
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(); } } }
-