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

Ric*_*ich 33 java android bitmap

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

是否有不同的呼叫我可以从一个应用程序资源可靠地获得一个可变的位图,而不需要第二个Bitmap对象(我可以创建一个可变的一个同样大小并绘制成帆布包裹它,但这需要两个大小相等的位图使用的内存是我预期的两倍)?

Der*_*rzu 61

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

我发现一个可接受的解决方案只使用一个位图的内存.

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

在这里查看完整的解决方案和实现:Android:将不可变位图转换为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;
}
Run Code Online (Sandbox Code Playgroud)

  • 真棒.感谢分享未来googlers的全面实施.因为我问过这个,所以我刚刚在记忆中这样做了,但是...总是潜在的OutOfMemoryException (3认同)
  • 为什么要使用外部存储并要求开发人员使用新权限?您应该改为使用内部存储。 (2认同)

小智 48

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

Bitmap bitmap= BitmapFactory.decodeResource(....);
bitmap= bitmap.copy(Bitmap.Config.ARGB_8888, true);
Run Code Online (Sandbox Code Playgroud)

  • 是的,但是大图像上的内存消耗太多了.我想我将不得不采取多步骤 - 使用BitmapFactory.Options.inJustDecodeBounds获取维度,创建一个可变的空位图,并从原始数据的输入流中填充它.得到我的是文档指定那些返回不可变位图的方法,并且此方法未指定(暗示默认为mutable).它在我的2.2手机上运行,​​但不是在我的好友的1.6手机上 (7认同)

Vai*_*air 27

我们可以首先通过实例化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);
Run Code Online (Sandbox Code Playgroud)

  • 为此+1,但仅在api级别11及以上可用.我的问题是如何向后兼容1.6(api 4). (2认同)

and*_*per 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;
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是使用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
Run Code Online (Sandbox Code Playgroud)

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