BitmapFactory.decodeResource在Android 2.2中返回一个可变的位图,在Android 1.6中返回一个不可变的位图

我正在开发一个应用程序,并在运行Android 2.2的设备上进行testing。 在我的代码中,我使用了一个使用BitmapFactory.decodeResource检索的位图,并且可以通过对其调用bitmap.setPixels()进行更改。 当我在运行Android 1.6的朋友的设备上testing这个时,我在调用bitmap.setPixels遇到了IllegalStateException 。 在线文档说,当位图是不可变的时, IllegalStateException从这个方法抛出。 这个文档没有decodeResource任何有关decodeResource返回一个不可变的位图的信息,但是显然必须是这样的。

是否有一个不同的调用,我可以使一个可变的位图可靠地从一个应用程序资源,而不需要第二个Bitmap对象(我可以创build一个可变的一个相同的大小,并绘制到包装它的canvas,但是这将需要两个相等的位图大小使用了两倍的内存,我想要的)?

您可以将您的不可变位图转换为可变位图。

我发现一个可接受的解决scheme,只使用一个位图的内存。

一个源位图是在磁盘(没有RAM内存)上原始保存的(RandomAccessFile),然后释放源位图(现在,在内存中没有位图),然后文件信息被加载到另一个位图。 通过这种方式,可以使每次只有一个位图存储在RAM存储器中的位图副本。

在这里看到完整的解决scheme和实现: Android:将不可变的位图转换为可变

我为这个解决scheme添加了一个改进,现在可以与任何types的位图(ARGB_8888,RGB_565等)一起使用,并删除临时文件。 看我的方法:

 /** * Converts a immutable bitmap to a mutable bitmap. This operation doesn't allocates * more memory that there is already allocated. * * @param imgIn - Source image. It will be released, and should not be used more * @return a copy of imgIn, but muttable. */ public static Bitmap convertToMutable(Bitmap imgIn) { try { //this is the file going to use temporally to save the bytes. // This file will not be a image, it will store the raw image data. File file = new File(Environment.getExternalStorageDirectory() + File.separator + "temp.tmp"); //Open an RandomAccessFile //Make sure you have added uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" //into AndroidManifest.xml file RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); // get the width and height of the source bitmap. int width = imgIn.getWidth(); int height = imgIn.getHeight(); Config type = imgIn.getConfig(); //Copy the byte to the file //Assume source bitmap loaded using options.inPreferredConfig = Config.ARGB_8888; FileChannel channel = randomAccessFile.getChannel(); MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, imgIn.getRowBytes()*height); imgIn.copyPixelsToBuffer(map); //recycle the source bitmap, this will be no longer used. imgIn.recycle(); System.gc();// try to force the bytes from the imgIn to be released //Create a new bitmap to load the bitmap again. Probably the memory will be available. imgIn = Bitmap.createBitmap(width, height, type); map.position(0); //load it back from temporary imgIn.copyPixelsFromBuffer(map); //close the temporary file and channel , then delete that also channel.close(); randomAccessFile.close(); // delete the temp file file.delete(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return imgIn; } 

你是对的,使用decodeResource你将得到一个不变的位图(为了testing它,你可以调用Bitmap对象的方法isMutable)。

要修改位图,可以使用复制方法创build此位图的可变版本,然后修改像素。

 Bitmap immutableBitmap = BitmapFactory.decodeResource(....); Bitmap mutableBitmap = immutableBitmap.copy(Bitmap.Config.ARGB_8888, true); 

我希望这些信息对你有用。

我们可以首先通过实例化一个BitmapFactory.Options类来设置BitmapFactory的选项,然后将名为“inMutable”的选项字段设置为true,然后将这个选项实例传递给decodeResource。

  BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inMutable = true; Bitmap bp = BitmapFactory.decodeResource(getResources(), R.raw.white, opt); 

下面是我创build的一个使用内部存储的解决scheme,不需要任何新的权限,基于“Derzu”的想法,以及从蜂窝开始,这是内置的:

 /**decodes a bitmap from a resource id. returns a mutable bitmap no matter what is the API level.<br/> might use the internal storage in some cases, creating temporary file that will be deleted as soon as it isn't finished*/ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static Bitmap decodeMutableBitmapFromResourceId(final Context context, final int bitmapResId) { final Options bitmapOptions = new Options(); if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) bitmapOptions.inMutable = true; Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bitmapResId, bitmapOptions); if (!bitmap.isMutable()) bitmap = convertToMutable(context, bitmap); return bitmap; } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public static Bitmap convertToMutable(final Context context, final Bitmap imgIn) { final int width = imgIn.getWidth(), height = imgIn.getHeight(); final Config type = imgIn.getConfig(); File outputFile = null; final File outputDir = context.getCacheDir(); try { outputFile = File.createTempFile(Long.toString(System.currentTimeMillis()), null, outputDir); outputFile.deleteOnExit(); final RandomAccessFile randomAccessFile = new RandomAccessFile(outputFile, "rw"); final FileChannel channel = randomAccessFile.getChannel(); final MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, imgIn.getRowBytes() * height); imgIn.copyPixelsToBuffer(map); imgIn.recycle(); final Bitmap result = Bitmap.createBitmap(width, height, type); map.position(0); result.copyPixelsFromBuffer(map); channel.close(); randomAccessFile.close(); outputFile.delete(); return result; } catch (final Exception e) { } finally { if (outputFile != null) outputFile.delete(); } return null; } 

另一种方法是使用JNI为了将数据放入它,回收原始位图,并使用JNI数据创build一个新的位图,这将是(自动)可变的,所以与我的位图JNI解决scheme一起,可以请执行以下操作:

 Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher); final JniBitmapHolder bitmapHolder=new JniBitmapHolder(bitmap); bitmap.recycle(); bitmap=bitmapHolder.getBitmapAndFree(); Log.d("DEBUG",""+bitmap.isMutable()); //will return true 

不过,我不确定API级别的最低要求是什么。 它在API 8及更高版本上运行得非常好。

我知道我迟到了,但是这就是我们如何避免这个令人痛苦的Android问题,并且裁剪并修改了一个内存中只有一个副本的图像。

情况
我们要处理保存到文件的图像的裁剪版本的像素。 对于高内存需求,我们绝不希望在任何给定的时间在内存中拥有这个映像的多个副本。

什么应该工作,但没有
BitmapRegionDecoder打开图像子部分(我们想要剪切的部分),传入一个带有inMutable = trueBitmapFactory.option ,处理像素然后保存到文件。
尽pipe我们的应用程序声明了API最小值为14,目标值为19,但BitmapRegionDecoder正在返回一个不可变的位图,实际上忽略了我们的BitMapFactory.options

什么都行不通

  • 使用BitmapFactory打开一个可变图像(尊重我们的inMutable选项)并剪裁它:所有裁剪技巧都是非破坏性的(要求整个图像的副本一次存储在内存中,即使在重写和回收之后立即收集垃圾)
  • BitmapRegionDecoder (有效裁剪)打开一个不可变的图像,并将其转换为可变的图像; 所有可用的技术都需要在内存中复制。

2014年的一个很好的解决scheme

  • BitmapFactory打开全尺寸的图像作为一个可变的位图,并执行我们的像素操作
  • 保存位图文件并从内存中回收它(它仍然是未裁剪)
  • BitmapRegionDecoder打开保存的位图,只打开要裁剪的区域(现在我们不关心位图是否是不可变的)
  • 将该位图(已被有效裁剪)保存到文件,覆盖之前保存的位图(未裁剪)

使用这种方法,我们可以在一个位图上裁剪和执行像素处理,只有一个副本在内存中(这样我们可以尽可能地避免那些烦人的OOM错误),交换RAM的时间,因为我们必须执行额外的(慢)文件IO的。

我知道这个问题已经解决了,但是呢:

BitmapFactory.decodeStream(getResources().openRawResource(getResources().getIdentifier("bitmapname", "drawable", context.getPackageName())))