BitmapFactory.decodeResource devuelve un mapa de bits mutable en Android 2.2 y un mapa de bits inmutable en Android 1.6
Estoy desarrollando una aplicación y probándola en mi dispositivo con Android 2.2. En mi código, hago uso de un mapa de bits que recuperar con BitmapFactory.decodeResource, y puedo hacer cambios llamando bitmap.setPixels()
en él. Cuando IllegalStateException
esto en el dispositivo de un amigo con Android 1.6, obtengo una IllegalStateException
en la llamada a bitmap.setPixels
. La documentación en línea dice que se IllegalStateException
una IllegalStateException
de este método cuando el mapa de bits es inmutable. La documentación no dice nada acerca de decodeResource
devolver un mapa de bits inmutable, pero claramente que debe ser el caso.
¿Hay una llamada diferente que puedo hacer para obtener un mapa de bits mutable confiable de un recurso de aplicación sin necesidad de un segundo objeto de Bitmap
(podría crear un mutable del mismo tamaño y dibujar en un lienzo, pero que requeriría dos mapas de bits de igual Tamaño utilizando hasta el doble de memoria que yo había previsto)?
- Android sin memoria al asignar variables
- ¿Cómo cargar imágenes grandes en Android y evitar el error de memoria?
- DecodeByteArray y copyPixelsToBuffer no funcionan. SkImageDecoder :: Factory devuelto null
- El mapa de bits rotativo hace que outOfMemoryException
- Imagen de la parte del androide en ImageView sin guardar en la tarjeta SD
- Cómo cambiar el tamaño de un mapa de bits en Android?
- Mapa de bits en ImageView con esquinas redondeadas
- "No se pueden dibujar mapas de bits reciclados" con Picasso
- El mapa de bits de la vista no se muestra en Android
- La mejor manera de mostrar la imagen en la vista de la cuadrícula es desplazarse suavemente
- Dibuja un mapa de bits en mosaico a través de un androide Rect
- Cómo cambiar el color de fondo de mapa de bits a transparente y de fondo no debe arrastrar?
- Android intentando usar un mapa de bits reciclado, no en mi código
Puede convertir su mapa de bits inmutable en un mapa de bits mutable.
Encontré una solución aceptable que utiliza sólo la memoria de un mapa de bits.
Un mapa de bits de origen es raw guardado (RandomAccessFile) en el disco (no memoria RAM), entonces se libera el mapa de bits de origen, (ahora, no hay mapa de bits en memoria) y después de eso, la información de archivo se carga en otro mapa de bits. De esta manera es posible hacer una copia de mapa de bits con sólo un mapa de bits almacenado en memoria ram por tiempo.
Vea la solución completa y la implementación aquí: Android: convierta Immutable Bitmap en Mutable
Agrego una mejora a esta solución, que ahora funciona con cualquier tipo de Bitmaps (ARGB_8888, RGB_565, etc), y elimina el archivo temporal. Ver mi método:
/** * 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; }
Usted tiene razón, usando decodeResource obtendrá un mapa de bits inmutable (para probarlo, puede llamar al método isMutable del objeto Bitmap).
Para modificar el mapa de bits, puede utilizar el método de copia para crear una versión mutable de este mapa de bits y, a continuación, modificar píxeles.
Bitmap immutableBitmap = BitmapFactory.decodeResource(....); Bitmap mutableBitmap = immutableBitmap.copy(Bitmap.Config.ARGB_8888, true);
Espero que esta información sea útil para usted.
Primero podemos configurar opciones para BitmapFactory instanciando una clase BitmapFactory.Options y luego establecer el campo de opciones llamado 'inMutable' como true y luego pasar esta instancia de opciones a decodeResource.
BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inMutable = true; Bitmap bp = BitmapFactory.decodeResource(getResources(), R.raw.white, opt);
He aquí una solución que he creado que utiliza el almacenamiento interno y no requiere ningún permiso nuevo, basado en la idea de "Derzu", y el hecho de que a partir de panal, esto está construido en:
/**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; }
Otra alternativa es usar JNI para poner los datos en él, reciclar el mapa de bits original y usar los datos JNI para crear un nuevo mapa de bits, que será (automáticamente) mutable, así que junto con mi solución JNI para mapas de bits , se puede Haz lo siguiente:
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
Sin embargo, no estoy seguro de cuál es el requisito mínimo de nivel API. Funciona muy bien en API 8 y superior.
Sé que llego tarde a la fiesta, pero así es como evitamos este doloroso problema de Android, y recorté y modificamos una imagen con sólo una copia en la memoria.
Situación
Queremos procesar los píxeles de una versión recortada de una imagen guardada en un archivo. Con altas demandas de memoria, nunca queremos tener más de una copia de esta imagen en la memoria en un momento dado.
Lo que debería haber funcionado pero no
Abrir la subsección de imagen (el bit con el que queríamos recortar) con BitmapRegionDecoder
, pasando en un BitmapFactory.option
con inMutable = true
, procesando los píxeles y luego guardándolo en el archivo.
Aunque nuestra aplicación declaró un API mínimo de 14 y un objetivo de 19, BitmapRegionDecoder
estaba devolviendo un mapa de bits inmutable, haciendo caso omiso de nuestro BitMapFactory.options
Lo que no funciona
- Abriendo una imagen mutable con
BitmapFactory
(que respeta nuestra opcióninMutable
) y croppping it: todas las técnicas de cultivo no son imperitivas (requieren una copia de toda la imagen para existir en la memoria a la vez, aunque la basura recogida inmediatamente después de sobrescribe y reciclado ) - La apertura de una imagen inmutable con
BitmapRegionDecoder
(efectivamente recortado) y la conversión a una mutable; Todas las técnicas disponibles requieren nuevamente una copia en memoria.
El gran trabajo de alrededor de 2014
- Abrir la imagen a tamaño completo con
BitmapFactory
como un mapa de bits mutable, y realizar nuestras operaciones de píxeles - Guardar el mapa de bits en el archivo y reciclarlo desde la memoria (todavía no se recorta)
-
BitmapRegionDecoder
mapa de bits guardado conBitmapRegionDecoder
, abriendo sólo la región para ser recortada a (ahora no nos importa si el mapa de bits es inmutable o no) - Guardar este mapa de bits (que efectivamente ha sido recortado) al archivo, sobrescribiendo el mapa de bits previamente guardado (que no se recortó)
Con este método, podemos recortar y realizar el procesamiento de píxeles en un mapa de bits con sólo 1 copia siempre en la memoria (por lo que podemos evitar esos molestos errores OOM tanto como sea posible), el comercio de memoria RAM para el tiempo, ya que tenemos que realizar extra (lenta) IOs.
Sé que la pregunta está resuelta, pero ¿qué pasa con:
BitmapFactory.decodeStream(getResources().openRawResource(getResources().getIdentifier("bitmapname", "drawable", context.getPackageName())))
- Cómo detectar la velocidad de la CPU android?
- IllegalStateException de Android: Fragmento no adjunto a Activity webview