OOM是Android 开发常见的bug,每当遇到这个,总是笑笑 表示手到擒来,
因为无非就是以下几种造成了OOM
1.Bitmap加载过多
如listview gridview等类似的viewgroup 需要展示大量的bitmap解决方案也很老套了:
- convertview和viewholder结合,
- lrucache配合本地缓存,网络加载 得三级缓存机制
- 从单个bitmap入手 进行 压缩质量 压缩尺寸操作
2.一些流,指针没有关闭,释放,如 数据库操作的cursor,stream类有没有close?bitmap有没有recycler? 复杂的多级引用 不使用的时候 有没有置为空,亦或者 手动释放掉?
加班得末尾暂时想到这些情况,日后补充,这些不是重点
重点是今天遇到的一个bug,让我对自己平时的工作 进行了足够深刻的反思。
如图所示,log中打印出了在箭头所指之处造成的OOM异常,
跳转进入当前activity 的时候,直接崩溃,
bug发生前提得补充:而且跳转时候崩溃这个情况不是必然发生得,一般不会OOM,只是偶尔应用撑不住了才会OOM
再解释一下 logOOM所指 该部分三行代码的意思:目的是使用viewpager创建 左右滑动的引导页,
for循环中 是为了生成包含bitmap的imageview,并且存储到 一个list集合中,viewpager 进行setAdapter加载适配的时候 需要用到list,这个大家都知道。
ok,为什么会出现在这里报错呢,
创建imageview对象会造成OOM崩溃?不至于,那是因为viewpager加载适配器?
可饮用viewpager 使用 container复用就足够了,只保留三页,剩下它自己就释放回收了,且不用考虑。
到底为什么会出现一个这样的错误呢? 居然是imageview.setBackgroundResource(resId);
各位请按着我的引导,第二个箭头标记之处,
createFromResourceStream() 创建了一个跟资源文件有关的流,重点是流,
接着往上看, BitmapFactory 必会的 decode系列 都跟stream有关,
一看到流 想到释放的问题,但是BitmapFactory系列的stream是系统提供的api,肯定不可能由咱们手动释放,那会是什么问题呢,
揭露答案前我也想了些许时间,因为是修改过其他的地方bug 才暴漏到这个问题的,其他代码不展示了,先说说这个地方的问题
一个小小的imageview为什么 会造成OOM?
思考之余,就在某一瞬间,我起我的大学老师杨顺民经常说的一句话:
创建了一个对象,申请了一个内存,啪啪啪。 ——杨顺民
对象,内存!
又突然想起 前几天整理笔记的一句话
如果分配出去的内存得不到释放,及时回收,就会引起系统运行速度下降,甚至导致程序瘫痪
这里是imageview的问题,但是单纯imageview 占用的内存并不大,大的原因是 从resource中加载 图片资源时,启动了流去加载,造成了字节数 溢出,表现为 OOM异常,程序崩溃
解决办法也很简单了,删除这些消耗内存的使用 或者 去检查其他地方的代码有没有内存泄露, 这里crash 是因为 内存不够用,不一定必然崩溃,so 得保证 项目 整体 不要出现过多的内存泄露。
PS:对于必须使用imageview setImageSource 的情况,不可避免申请内存, 那我也提供一种解决方案 在本篇文章得 案例部分
这里提炼出一个关键字,也是我今天才意识到,想起来的—-字节数
【其实这个bug之前 表现得事bytearrystream 什么什么的异常,我忘记了,改了改,改到这里,暴露出imageview的问题】
字节数 才是内存的根本,我们new 出来的对象,在内存区域中开辟了一个空间 给这个对象,它就占用了内存,内存的形式 应该就是字节形式【计算机中数据存储 都是以 字节存在的】
所以今天乃至之前经常看到得bytearraystream 之类的bug 就是因为Android 系统分配给应用的内存【内存大小其实就是 字节数量】不够用了,所以项目使用图片越多,对于一些细节应该扣得越细,之前做其他项目的琐碎的经验 以及 对内存管理掌握得不到位,导致我没有意识到字节数这个根本的问题。
还好 苦口婆心得 杨老师提醒了我哈哈。
可能这个问题对于大多数有经验的人来说 手到擒来,对于我 也没有费太多时间,但是 思考过程大于结果,学习就是不断思考的过程,哪怕是错的,都会有收获。
1.内存根本就是字节,OOM 就是要抠代码细节,对于每个对象 是否会造成内存申请过大 没有释放造成泄露 都要铭记于心
今天做项目,发现需要显示一张超大图片,处理过后,还有561Kb
加载的时候,就crash --- OOM shortMsg:java.lang.OutOfMemoryError longMsg:java.lang.OutOfMemoryError: bitmap size exceeds VM budget stackTrace:java.lang.OutOfMemoryError: bitmap size exceeds VM budget at android.graphics.Bitmap.nativeCreate(Native Method) at android.graphics.Bitmap.createBitmap(Bitmap.java:477) at android.graphics.Bitmap.createBitmap(Bitmap.java:444) at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:349) at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:512) at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:487) at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
代码如下:
detailView=(ImageView)findViewById(R.id.detailView);
detailView.setBackgroundResource(R.drawable.more_info);//this line will lead to OOM
换成这种:
detailView.setImageResource(R.drawable.more_info); //也同样会OOM
后来找到了solution:
/** * 以最省内存的方式读取本地资源的图片 * @param context *@param resId * @return */
public static Bitmap readBitMap(Context context, int resId){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
//获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is,null,opt);
}
取得bitmap之后,再 detailView.setImageBitmap(pdfImage); 就ok了!
那是为什么,会导致oom呢:
原来当使用像 imageView.setBackgroundResource,imageView.setImageResource, 或者
BitmapFactory.decodeResource 这样的方法来设置一张大图片的时候,这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。
因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的
source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。如果在读取时加上图片的Config参数,可以跟有效减少加载的内存,从而跟有效阻止抛out
of Memory异常。另外,需要特别注意:
decodeStream是直接读取图片资料的字节码了, 不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源,否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。
下面 配合案例,修改了自己使用res资源创建裁剪bitmap 的函数
/** * 传入path,根据view的宽高来获取图片缩略图 * @param path 注意是资源id哟,不是string 路径 * @param viewWidth * @param viewHeight * @return * * 11/18 修改为BitmapFactory.decodeStream * 内存 每次少占用了10M左右, */
private Bitmap decodeThumbBitmapForFile(int path, int viewWidth, int viewHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
//表示解析bitmap,但不占用内存
options.inJustDecodeBounds = true;
InputStream is = mContext.getResources().openRawResource(path);
BitmapFactory.decodeStream(is, null, options);
// BitmapFactory.decodeResource(mContext.getResources(),path);
//设置缩放比率,
options.inSampleSize = computeScale(options, viewWidth, viewHeight);
//设置false,加入到内存中
options.inJustDecodeBounds = false;
return BitmapFactory.decodeStream(is, null, options);
}
调用了computeScale函数,那我也干脆贴上来吧
/** * ¸根据view的宽高计算bitmap缩放比例 * @param options */
private int computeScale(BitmapFactory.Options options, int viewWidth, int viewHeight){
int inSampleSize = 1;
if(viewWidth == 0 || viewHeight == 0){
return inSampleSize;
}
int bitmapWidth = options.outWidth;
int bitmapHeight = options.outHeight;
//假如Bitmap的宽度或高度大于我们设定图片的View的宽高,则计算缩放比例
if(bitmapWidth > viewWidth || bitmapHeight > viewWidth){
int widthScale = Math.round((float) bitmapWidth / (float) viewWidth);
int heightScale = Math.round((float) bitmapHeight / (float) viewWidth);
//为了保证图片不缩放变形,我们取宽高比例最小的那个
inSampleSize = widthScale < heightScale ? widthScale : heightScale;
}
return inSampleSize;
}
测试高清大图的时候,adapter中 每个imageview 申请 的内存减弱了10M左右!提升效果很明显!
over!