BitmapFactory.decodeResource返回Android 2.2中的可变位图和Android 1.6中的不可变位图

时间:2010-12-03 19:19:40

标签: java android bitmap

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

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

7 个答案:

答案 0 :(得分:59)

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

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

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

请参阅此处的完整解决方案和实施:Android: convert Immutable Bitmap into Mutable

我添加了对此解决方案的改进,现在可以使用任何类型的位图(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;
}

答案 1 :(得分:46)

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

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

答案 2 :(得分:25)

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

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

答案 3 :(得分:6)

这是我创建的一个解决方案,它使用内部存储,不需要任何新的权限,基于“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数据创建一个新的位图,该位图将(自动)可变,因此与my JNI solution for bitmaps一起使用,人们可以做到以下几点:

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及更高版本上运行良好。

答案 4 :(得分:2)

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

<强>场合
我们想要处理保存到文件的图像的裁剪版本的像素。由于存储器需求量很大,我们绝不希望在任何给定时间内在内存中存储多个此映像副本。

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

什么不行?

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

2014年的出色工作

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

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

答案 5 :(得分:0)

发生这种情况是因为您想通过调用 setHeight()setWidth()

来调整位图的大小

调整任何位图或可绘制对象(Png、Svg、矢量等)的大小

public Bitmap editMyBitmap(int drawableId, int newHeight, int newWidth) {
        Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), drawableId);
        myBitmap = Bitmap.createScaledBitmap(myBitmap, newWidth, newHeight, false);
        return myBitmap;
    }

使用示例:

Bitmap facebookIcon = editMyBitmap(R.drawable.facebookImage);
// now use it anywhere
imageView.setBitmapImage(facebookIcon); 
canvas.drawBitmap(facebookIcon, 0, 0, null); 

答案 6 :(得分:-1)

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

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