说明这个问题,首先来看一下实际的内存占用情况。
我们创建一个最简单的android应用,一个Activity,内容是一张图片,图片放在drawable-hdpi目录下。布局文件:
android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/welcome" android:id="@+id/splash_layout"> 图片文件大小145k。 打开DDMS,查看内存占用的情况: 在米1(小屏手机)上的内存占用情况: 而在大屏手机上显示的内存占用情况: 内存占用了12M,1-byte array(byte,boolean)栏中,内存占了8.5M,而最大一个对象占了4.16M,应该就是图片。而实际上图片大小也就145K。之所以大屏手机会出现这种情况,主要是因为我们把图片放在hdpi目录下,而大屏手机实际上是xhdpi甚至是xxhdpi。根据Android官方文档提供的资料: By default, Android scales your bitmapdrawables (.png,.jpg, and.giffiles) and Nine-Patch drawables (.9.pngfiles) sothat they render at the appropriate physical size on each device. For example,if your application provides bitmap drawables only for the baseline, mediumscreen density (mdpi), then the systemscales them up when on ahigh-density screen, and scales them down when on a low-densityscreen. Android会自动拉伸图片以适应当前手机的分辨率。因此导致了OOM内存溢出。 那么如果我们把hdpi的图片复制一份到xhdpi和xxhdpi中会怎样? 查看运行的结果: 在米1(小屏手机)上的内存占用情况: 而在大屏手机上显示的内存占用情况: 内存已经有所减少,特别是1-bytearray(byte[], boolean[])这一行。米1由于对应的是hdpi的图片,所以不受影响。 但是这样做也有问题,首先我们得把图片都拷贝到不同分辨率对应的目录下(最正确的做法是分别做一套对应分辨率的图片);其次图片占用内存的大小虽然已经减小,但还是太大。 再想一想,我们在布局文件里面写android:background和android:src这些属性,实际上解析之后执行的是view.setBackgroundResource和view.setImageResource方法,这两个方法实际上是拿到资源ID再去获取资源的drawable。他们会decode图片后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。 实际上我们可以用decodeStream来替代,因为decodeStream直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间.另外我们可以设置图片的参数,例如设置为Bitmap.Config.RGB_565来减少内存开销。因为在android文档中描述Bitmap.Config.RGB_565每一个像素存在2个字节中,而默认的Bitmap.Config.ARGB_8888每一个像素则需要4个字节,理论上足足节省了一半空间。 了解这些之后,我把布局文件中的android:background去掉,在java文件中来设置背景。 如下: BitmapFactory.Options opt = newBitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.RGB_565; opt.inPurgeable = true; opt.inInputShareable = true; //获取资源图片 InputStream is = context.getResources().openRawResource(resId); Bitmap bitmap = BitmapFactory.decodeStream(is,null, opt); is.close(); returnnew BitmapDrawable(context.getResources(),bitmap); 运行之后在来看效果: 在米1(小屏手机)上的内存占用情况: 而在大屏手机上显示的内存占用情况: 内存瞬间降下来,1-bytearray(byte[], boolean[])这一行最大值为151K。 =================另外一篇解释============= 用setBackgroundResource显示多张图片时,会出现oom, 在看了setBackgroundResource的源码以后,恍然大悟,android对于直接通过资源id载入的资源其实是做了cache的了,这样下次再需要此资源的时候直接从cache中得到,这也是为效率考虑。但这样做也造成了用过的资源都会在内存中,这样的设计不是很适合使用了很多大图片资源的应用,这样累积下来应用的内存峰值是很高的。 代码如下: 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: /** BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.RGB_565; opt.inPurgeable = true; opt.inInputShareable = true; InputStream is = getResources().openRawResource( R.drawable.splash ); Bitmap bm = BitmapFactory.decodeStream(is, null, opt); BitmapDrawable bd = new BitmapDrawable(getResources(), bm); holder.iv.setBackgroundDrawable(bd); 取得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中配置相应的图片资源,否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。 android中用setBackgroundResource加载图片时出现oom
* 以最省内存的方式读取本地资源的图片
* @param context
*@param resId
* @return
*/