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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4349075/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 16:21:56  来源:igfitidea点击:

BitmapFactory.decodeResource returns a mutable Bitmap in Android 2.2 and an immutable Bitmap in Android 1.6

javaandroidbitmap

提问by Rich

I am developing an application and testing it on my device running Android 2.2. In my code, I make use of a Bitmap that I retrieve using BitmapFactory.decodeResource, and I am able to make changes by calling bitmap.setPixels()on it. When I test this on a friend's device running Android 1.6, I get an IllegalStateExceptionin the call to bitmap.setPixels. Documentation online says an IllegalStateExceptionis thrown from this method when the bitmap is immutable. The documentation doesn't say anything about decodeResourcereturning an immutable bitmap, but clearly that must be the case.

我正在开发一个应用程序并在运行 Android 2.2 的设备上测试它。在我的代码中,我使用了我使用 BitmapFactory.decodeResource 检索的位图,并且我能够通过调用bitmap.setPixels()它来进行更改。当我测试这个运行Android 1.6朋友的设备上,我得到一个IllegalStateException在调用bitmap.setPixels。在线文档说,IllegalStateException当位图不可变时,此方法会抛出an 。该文档没有说明decodeResource返回不可变位图的任何内容,但显然必须如此。

Is there a different call I can make to get a mutable bitmap reliably from an application resource without needing a second Bitmapobject (I could create a mutable one the same size and draw into a Canvas wrapping it, but that would require two bitmaps of equal size using up twice as much memory as I had intended)?

是否有不同的调用可以从应用程序资源可靠地获取可变位图,而无需第二个Bitmap对象(我可以创建一个相同大小的可变位图并绘制到包装它的 Canvas 中,但这需要两个相同大小的位图使用的内存是我预期的两倍)?

采纳答案by Derzu

You can convert your immutable bitmap to a mutable bitmap.

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

I found an acceptable solution that uses only the memory of one bitmap.

我找到了一个可接受的解决方案,它只使用一个位图的内存。

A source bitmap is raw saved (RandomAccessFile) on disk (no ram memory), then source bitmap is released, (now, there's no bitmap at memory), and after that, the file info is loaded to another bitmap. This way is possible to make a bitmap copy having just one bitmap stored in ram memory per time.

源位图在磁盘上原始保存(RandomAccessFile)(无 ram 内存),然后源位图被释放,(现在,内存中没有位图),然后将文件信息加载到另一个位图。这种方式可以制作一个位图副本,每次只在 ram 内存中存储一​​个位图。

See the full solution and implementation here: Android: convert Immutable Bitmap into Mutable

在此处查看完整的解决方案和实现:Android:将不可变位图转换为可变位图

I add a improvement to this solution, that now works with any type of Bitmaps (ARGB_8888, RGB_565, etc), and deletes the temp file. See my method:

我对此解决方案进行了改进,它现在适用于任何类型的位图(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;
}

回答by Jesus Oliva

Copy the bitmap to itself with mutable option true. This way neither extra memory consumption nor long lines of codes are needed.

使用可变选项 true 将位图复制到自身。这样既不需要额外的内存消耗,也不需要很长的代码行。

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

回答by Vaishak Nair

We can first set options for BitmapFactory by instantiating an BitmapFactory.Options class and then set the options field named 'inMutable' as true and and then pass this options instance to decodeResource.

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

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

回答by android developer

Here's a solution i've created that uses the internal storage and doesn't require any new permission, based on "Derzu"'s idea, and the fact that starting with honeycomb, this is built in :

这是我创建的一个解决方案,它使用内部存储并且不需要任何新权限,基于“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;
}

another alternative is to use JNI in order to put the data into it, recycle the original bitmap, and use the JNI data to create a new bitmap, which will be (automatically) mutable, so together with my JNI solution for bitmaps, one can do the following:

另一种选择是使用 JNI 将数据放入其中,回收原始位图,并使用 JNI 数据创建一个新位图,该位图将(自动)可变的,因此与我的位图 JNI 解决方案一起,可以请执行下列操作:

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

however, i'm not sure what is the minimal requirement of API level. it works very well on API 8 and above.

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

回答by Entretoize

I know the question is solved but what about:

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

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

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

回答by Anti Earth

I know I'm late to the party, but this is how we avoided this painfully annoying Android problem, and cropped and modified an image with only one copy ever in memory.

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

Situation
we want to process the pixels of a cropped version of an image saved to file. With high memory demands, we never want to have more than one copy of this image in memory at any given time.

现状
我们要处理的保存文件的图像的裁剪版本的像素。由于内存需求很高,我们永远不希望在任何给定时间在内存中拥有多个此图像的副本。

What should have worked but didn't
Opening the image subsection (the bit we wanted to crop to) with BitmapRegionDecoder, passing in a BitmapFactory.optionwith inMutable = true, processing the pixels then saving to file.
Though our app declared an API minimum of 14 and a target of 19, BitmapRegionDecoderwas returning an immutable bitmap, effectively ignoring our BitMapFactory.options

什么应该有效但没有
打开图像子部分(我们想要裁剪的位) with BitmapRegionDecoder,传入BitmapFactory.optionwith inMutable = true,处理像素然后保存到文件。
尽管我们的应用程序声明 API 最少为 14,目标为 19,BitmapRegionDecoder但返回的是不可变位图,实际上忽略了我们的BitMapFactory.options

What won't work

什么行不通

  • opening an mutable image with BitmapFactory(which respects our inMutableoption) and croppping it: all cropping techniques are non-imperitive (require a copy of the entire image to exist in memory at a time, even if garbage collected immediately after with overwrites and recycling)
  • opening an immutable image with BitmapRegionDecoder(effectively cropped) and converting it to a mutable one; all available techniques again require a copy in memory.
  • 打开可变图像BitmapFactory(尊重我们的inMutable选择)并裁剪它:所有裁剪技术都是非强制性的(需要一次在内存中存在整个图像的副本,即使在覆盖和回收后立即收集垃圾)
  • 打开一个不可变的图像BitmapRegionDecoder(有效裁剪)并将其转换为可变的图像;所有可用的技术都需要在内存中进行复制。

The great work-around of 2014

2014 年的伟大工作

  • open the full size image with BitmapFactoryas a mutable bitmap, and perform our pixel operations
  • save the bitmap to file and recycle it from memory (it's still un-cropped)
  • open the saved bitmap with BitmapRegionDecoder, opening only the region to be cropped to (now we don't care if the bitmap is immutable or not)
  • save this bitmap (which has effectively been cropped) to file, overwriting the previously saved bitmap (which was un-cropped)
  • BitmapFactory使用可变位图打开全尺寸图像,并执行我们的像素操作
  • 将位图保存到文件并从内存中回收它(它仍然是未裁剪的)
  • 打开保存的位图BitmapRegionDecoder,只打开要裁剪的区域(现在我们不在乎位图是否不可变)
  • 将此位图(已被有效裁剪)保存到文件中,覆盖之前保存的位图(未裁剪)

With this method, we can crop and perform pixel processing on a bitmap with only 1 copy ever in memory (so we can avoid those pesky OOM errors as much as possible), trading RAM for time as we have to perform extra (slow) file IOs.

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