关于bitmap的一些知识

    xiaoxiao2021-03-25  92

         最近在做一个弹幕的功能,涉及到弹幕头像、礼物等下载、缓存等。使用了开源的<Android 开源框架Universal-Image-Loader>,做起来比较顺手。原因是该开源框架比较完善,调用者很容易上手,用起来和方便。在此就不说开源框架了,其实自己要写一个开源框架,最主要的是把原理弄懂。先谈一谈这个bitmap吧。有些知识是从网络摘操,因为写的很好。

        bitmap是位图,它将图像定义为由点(像素)组成,每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩。例如,一幅1024×768分辨率的32位真彩图片,其所占存储字节数为:1024×768×32/(8*1024)=3072KB。

        Android中展示不同格式的图像,在内存中都是一个二进制数据。都是用Bitmap对象存储的。这个内存包括图片的像素数据和本身对象。图像占用内存过多会导致OOM。但是不恰当的使用,使得OOM发生的几率更大。那为什么会发生OOM?相信大家都知道,Android系统分配给每个应用程序的内存是有限的(在Android 3.1以及更高的版本中,可以在AndroidManifest.xml的application标签中增加一个值等于“true”的android:largeHeap属性来通知Dalvik虚拟机应用程序需要使用较大的Java Heap,)。当使用内存超过限定,就会发生。所以,控制好图片内存是非常关键的。经过Android系统的更新,不同版本对Bitmap的存储有些差异。

      在Android2.3.3(API 10)及之前的版本中,Bitmap对象与其像素数据是分开存储。 Bitmap对象存储在Dalvik heap中,而Bitmap对象的像素数据则存储在Native Memory(本地内存)中或者说Derict Memory(直接内存)中。 这使得存储在Native Memory中的像素数据的释放是不可预知的,我们可以调用recycle()方法来对Native Memory中的像素数据进行释放。 前提是你可以清楚的确定Bitmap已不再使用了,如果你调用了Bitmap对象recycle()之后再将Bitmap绘制出来,就会出现"Canvas: trying to use a recycled bitmap"错误。

     在Android3.0(API 11)之后,Bitmap的像素数据和Bitmap对象一起存储在Dalvik heap中,所以我们不用手动调用recycle()来释放Bitmap对象,内存的释放都交给垃圾回收器来做。2.3以后采用并发垃圾回收,会通过垃圾回收算法检查对象是否可以回收(不同的垃圾回收器具有不同的回收算法),如果在回收器没有及时回收图片,或者某个时候创建大量的bitmap,此时就有可能会OOM。

      那么怎么恰当的处理图片减少OOM的几率呢?

      1.在Android2.3以下,使用图片,先判断图片是否有效即是否已经回收,未回收则需用recycle()方法来释放内存。比如在大量图片在加载的listview,gridview中,这可以通过计数器记录该图片引用次数,在未被引用情形下,及时回收图片。如下:

    private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); }   2.在Android以上,垃圾回收主动回收图片,所以不用考虑回收问题。但是,因为图片的解码要耗大量内存,可以在图片解码做优化处理。怎么处理呢?

     (1)BitmapFactory.Options第一次先获取图片大小,再和要显示的view大小做一个scale,之后再做解码为view大小的图片。这样,如果原图很大,显示的view很小,就不会占用太多内存。如下:

    public static Bitmap decodeStream(InputStream is, Config pixelFormat, int outWidth, int outHeight, int maxWidth, int maxHeight) { byte[] decode_buf = _local_buf.get(); if (decode_buf == null) { decode_buf = new byte[64 * 1024]; _local_buf.set(decode_buf); } try { BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inTempStorage = decode_buf; opts.inPreferredConfig = pixelFormat; opts.inJustDecodeBounds = true; //先获取图片大小 is.mark(64 * 1024); BitmapFactory.decodeStream(is, null, opts);//第一次解码获取图片打下 try { is.reset(); } catch (IOException e) { e.printStackTrace(); is.close(); return null; } if ((outWidth > 0 && outHeight >= 0) || (outWidth >= 0 && outHeight > 0) || (maxWidth > 0 && maxHeight >= 0) || (maxWidth >= 0 && maxHeight > 0)) { opts.inScaled = true; opts.inTargetDensity = DisplayMetrics.DENSITY_DEFAULT; int scale_x; int scale_y; if (maxWidth > 0 || maxHeight > 0) { if (outHeight == 0 && outWidth == 0) { outWidth = Math.min(opts.outWidth, maxWidth); outHeight = Math.min(opts.outHeight, maxHeight); } else { if (outWidth > 0 && maxWidth > 0) { outWidth = Math.min(outWidth, maxWidth); } if (outHeight > 0 && maxHeight > 0) { outHeight = Math.min(outHeight, maxHeight); } } } if (outWidth == 0) { scale_x = scale_y = opts.inTargetDensity * opts.outHeight / outHeight; } else if (outHeight == 0) { scale_x = scale_y = opts.inTargetDensity * opts.outWidth / outWidth; } else { scale_x = opts.inTargetDensity * opts.outWidth / outWidth; scale_y = opts.inTargetDensity * opts.outHeight / outHeight; } opts.inDensity = Math.min(scale_x, scale_y); if (opts.inDensity == opts.inTargetDensity) { opts.inScaled = false; outWidth = opts.outWidth; outHeight = opts.outHeight; } else { outWidth = XulUtils.roundToInt((float) opts.outWidth * opts.inTargetDensity / opts.inDensity); outHeight = XulUtils.roundToInt((float) opts.outHeight * opts.inTargetDensity / opts.inDensity); } } else { outWidth = opts.outWidth; outHeight = opts.outHeight; } Log.i(TAG, " width " + opts.outWidth + " height " + opts.outHeight + " ==> width " + outWidth + " height " + outHeight); opts.inJustDecodeBounds = false; opts.inPurgeable = false; opts.inMutable = true; if (!opts.inScaled ) { opts.inBitmap = createBitmapFromRecycledBitmaps(opts.outWidth, opts.outHeight, pixelFormat);//可以设置图片重用, } else { ++_newCount; } opts.inSampleSize = 1; Bitmap bm = BitmapFactory.decodeStream(is, null, opts);//第二次解码图片,ops带scale return bm; } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return null; }   以上代码注意第一次设置inJustDecodeBounds为true,获取图片尺寸。再进行解码获取图片。

      另外BitmapFactory.Options.inBitmap的这个字段,假如这个字段被设置了,在解码Bitmap的时候,就会去重用inBitmap设置的Bitmap,减少内存的分配和释放,提高了应用的性能(该参数不是用来减少内存占用,而是用来内存重用,避免大内存块的申请与释放。)。因为内存的释放(GC)可能影响所有线程绘制。

       什么条件会被重用?

       Android 4.4之前,设置的Bitmap只有同等大小的图片所占用的内存才能被重用。 即宽高相等,且inSampleSize为1。 而4.4之后你可以重用任何bitmap的内存区域,只要这块内存比将要分配内存的bitmap大就可以。例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。解码新申请的bitmap与旧的bitmap必须有相同的解码格式。那么就不能支持4444与565格式的bitmap了,不过可以通过创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用。

      在实际的项目中,有些时候设备在设置重用图片后,会导致图片出现花屏的现象,即解码失败导致。这样的情况,就不要开启重用图片的功能。

     

    转载请注明原文地址: https://ju.6miu.com/read-23844.html

    最新回复(0)