android 慎用drawable中大图,造成内存溢出的解决方案

drawable中存放一些本地图片,用来在需要的地方调用,这是很常见的用法。

而近期,新版本应用即将上线,因此我将应因上传至阿里移动质检平台测试了一翻,检测出来的问题并不是很多,但在30台机器中,有三次内存溢出的情况出现,这让我很担心。



点开后查看详情:


分析

通过上图大致知道是引导页面出现的问题。为了更好的分析原因,决定通过MAT工具来分析内存占用情况。

在android studio中通过Android Monitor界面(查看log日志的那个)中的Monitors中的Dump java heap按钮,就可以在应用运行过程中实时的捕获内存的hprof文件。


捕获后可以在界面左侧的capture中看到文件



找到文件,通过sdk中的platform-tools文件夹内的hprof-conv工具来转换文件


在此文件夹内按住shift 同时鼠标右键点击打开命令行界面 

输入 hprof-conv 你的源hprof文件路径.hprof  目标路径.hprof

就可以完成转换了。


下面就可以通过MAT(Memory Analyaer Tool)工具来分析。MAT工具下载


MAT分析

通过mat工具打开刚才转换过的文件后,看到了下面的内存占用情况,总的占用52.0MB,在只有一个页面(内有5张图片)的情况下占用这么多是不合理的



点击左下侧的histogram,看到byte[]相对于其它占用了大量的内存



右键点击byte[],选择List objects-->with incoming references(如果到这里有类似:MAT是啥?不认识啊!等感觉,那么可以先自行去查一下MAT工具的使用了,这里不作详细说明)


打开之后如下


很明显,占用内存的大头就是前面几个,点开看看



发现bitmap,原来就是图片的原因(大多数内存溢出都是这个原因 0.0)

为了看是哪个图片,这里要借助一个图片工具:GIMP 下载点这里

选中mBuffer

然后右边会出现这个:


嗯,再右键我上面红箭头指向的mBuffer选项



把数据保存为文件,注意了,要以xxx.data形式保存



然后打开GIMP把这个bitmap.data拖进去

会出现这个:


选中RGB Alpah ,另外看到宽高这里,通过MAT工具是可以知道数据的,回到MAT,还是选中那个mBuffer,找到数据填进去就可以



填 好后,已经可以看到图片了:

(注意,为了自已的小隐私,这里打码了)



已经知道了,这就是引导页面的图片,但是查看文件


有7.9MB,天,我传到drawable去的时候只有100kb,怎么回事呢,然后看下另外的byte[]


全是一样的大小,说明不是图片的问题,应该是调用过程中的问题。

查看代码,这里引导页面是用viewpager实现的,这里只贴instantiateItem()的代码:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public Object instantiateItem(ViewGroup container, int position) {  
  3.  ....  
  4.     view.setImageResource(mGuides[position]);  
  5.   
  6.  ....  
  7.       
  8. }  

看来看去只发现view.setImageResource(mGuides[position]);这代码是用来设置图片的,问题应该就出在这里了。

原因

是在drawable中直接引用的,去查了一翻之后,原来直接使用setImageResource方法,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。

解决方法:

改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,

查看一下decodeStream方法的源码:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {  
  2.        // we don't throw in this case, thus allowing the caller to only check  
  3.        // the cache, and not force the image to be decoded.  
  4.        if (is == null) {  
  5.            return null;  
  6.        }  
  7.   
  8.        Bitmap bm = null;  
  9.   
  10.        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");  
  11.        try {  
  12.            if (is instanceof AssetManager.AssetInputStream) {  
  13.                final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();  
  14.                bm = nativeDecodeAsset(asset, outPadding, opts);  
  15.            } else {  
  16.                bm = decodeStreamInternal(is, outPadding, opts);  
  17.            }  
  18.   
  19.            if (bm == null && opts != null && opts.inBitmap != null) {  
  20.                throw new IllegalArgumentException("Problem decoding into existing bitmap");  
  21.            }  
  22.   
  23.            setDensityFromOptions(bm, opts);  
  24.        } finally {  
  25.            Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);  
  26.        }  
  27.   
  28.        return bm;  
  29.    }  


发现decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,
无需再使用java层的createBitmap,从而节省了java层的空间。

通过这个方法:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public static Bitmap readBitMap(Context context, int resId) {  
  2.         BitmapFactory.Options opt = new BitmapFactory.Options();  
  3.         opt.inPreferredConfig = Bitmap.Config.RGB_565;  
  4.         opt.inPurgeable = true;  
  5.         opt.inInputShareable = true;  
  6.         //获取资源图片  
  7.         InputStream is = context.getResources().openRawResource(resId);  
  8.         return BitmapFactory.decodeStream(is, null, opt);  
  9.     }  

获得bitmap后设置给imageview,就可以了。

再查看内存:




 

发现从原先的52MB,减小到了14.4MB。原先加载一张图片需要7.9MB,总共5张,大概要40MB,这里就节约了近40MB的内存。

总结

在使用中,小的图片,可以通过drawable直接引用,但涉及到大图时,尽量通过decodeStream来创建bitmap,然后再给对应的view使用。

转载:http://blog.csdn.net/chniccs/article/details/51373623

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值