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 = true
的BitmapFactory.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())))