Cómo almacenar mapas de bits en memoria nativa

Para mis 10,000 puntos, he decidido contribuir con algo con este genial sitio web: un mecanismo para almacenar en caché bitmaps en la memoria nativa.

Fondo

Los dispositivos Android tienen una cantidad muy limitada de memoria para cada aplicación: el montón varía de 16 MB a 128 MB, dependiendo de varios parámetros .

Si supera este límite, obtiene OOM, y esto puede ocurrir muchas veces cuando usa bitmaps.

Muchas veces, una aplicación puede necesitar superar esas limitaciones, realizar operaciones pesadas en grandes mapas de bits o simplemente almacenarlos para usarlos más adelante, y necesita

Lo que se me ocurrió, es una clase Java simple que haría las cosas un poco más fáciles para esos propósitos.

Está utilizando JNI para almacenar los datos de mapas de bits, y poder restaurarlos cuando sea necesario.

Para soportar múltiples instancias de la clase, tuve que usar un truco que encontré ( aquí ).

Notas importantes

  • Los datos aún se almacenan en la memoria RAM, por lo que si el dispositivo no tiene suficiente memoria RAM, la aplicación podría ser eliminada.

  • Recuerde liberar la memoria tan pronto como pueda. no solo para evitar memory leaks, sino también para evitar que el sistema dé prioridad a la muerte, una vez que la aplicación pasa al segundo plano.

  • Si no quiere olvidarse de liberar la memoria, puede liberarla cada vez que restaura el bitmap, o hacer que la clase implemente Cerrable .

  • Como medida de seguridad, he hecho que libere automáticamente su memoria nativa en el método finalize (), pero no permita que sea responsable del trabajo. es muy arriesgado también lo hice escribir en el registro cuando ocurre tal cosa.

  • La forma en que funciona es copiando todos los datos en objetos JNI, y para restaurarlos, crea el bitmap desde cero y coloca los datos dentro.

  • Los mapas de bits que se utilizan y restauran están en formato ARGB_8888. por supuesto, puedes cambiarlo a lo que quieras, solo no olvides cambiar el código …

  • Los grandes mapas de bits podrían demorar en almacenar y restaurar, por lo que sería aconsejable hacerlo en un hilo de fondo.

  • Esta no es una solución OOM completa, pero podría ser útil. por ejemplo, puede usarlo junto con su propio LruCache, mientras evita usar la memoria del montón para el caché.

  • El código es solo para almacenar y restaurar. si necesita realizar algunas operaciones, deberá realizar algunas investigaciones. openCV podría ser la respuesta, pero si desea realizar algunas cosas básicas, puede implementarlas usted mismo ( aquí hay un ejemplo de imágenes grandes rotatign utilizando JNI). si conoce otras alternativas, hágamelo saber, aquí .

Espero que esto sea útil para algunas personas. Por favor escribe tus comentarios.

Además, si encuentra algún problema con el código o una sugerencia de mejora, hágamelo saber.


Mejor solución

Si desea realizar aún más operaciones en el lado de JNI, puede usar esta publicación que hice. está basado en el código que he escrito aquí, pero te permite hacer más operaciones y puedes agregar fácilmente más tuyo.

explicación

el código de muestra muestra cómo almacenar 2 mapas de bits diferentes (pequeños, pero es solo una demostración), reciclar los Java originales y luego restaurarlos en instancias Java y usarlos.

como se puede adivinar, el diseño tiene 2 imageViews. No lo incluí en el código ya que es bastante obvio.

recuerde cambiar el código a su propio paquete si lo necesita, de lo contrario las cosas no funcionarán.

MainActivity.java – cómo usar:

package com.example.jnibitmapstoragetest; ... public class MainActivity extends Activity { @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher); final JniBitmapHolder bitmapHolder=new JniBitmapHolder(bitmap); bitmap.recycle(); // Bitmap bitmap2=BitmapFactory.decodeResource(getResources(),android.R.drawable.sym_action_call); final JniBitmapHolder bitmapHolder2=new JniBitmapHolder(bitmap2); bitmap2.recycle(); // setContentView(R.layout.activity_main); { bitmap=bitmapHolder.getBitmapAndFree(); final ImageView imageView=(ImageView)findViewById(R.id.imageView1); imageView.setImageBitmap(bitmap); } { bitmap2=bitmapHolder2.getBitmapAndFree(); final ImageView imageView=(ImageView)findViewById(R.id.imageView2); imageView.setImageBitmap(bitmap2); } } } 

JniBitmapHolder.java – el “puente” entre JNI y JAVA:

 package com.example.jnibitmapstoragetest; ... public class JniBitmapHolder { ByteBuffer _handler =null; static { System.loadLibrary("JniBitmapStorageTest"); } private native ByteBuffer jniStoreBitmapData(Bitmap bitmap); private native Bitmap jniGetBitmapFromStoredBitmapData(ByteBuffer handler); private native void jniFreeBitmapData(ByteBuffer handler); public JniBitmapHolder() {} public JniBitmapHolder(final Bitmap bitmap) { storeBitmap(bitmap); } public void storeBitmap(final Bitmap bitmap) { if(_handler!=null) freeBitmap(); _handler=jniStoreBitmapData(bitmap); } public Bitmap getBitmap() { if(_handler==null) return null; return jniGetBitmapFromStoredBitmapData(_handler); } public Bitmap getBitmapAndFree() { final Bitmap bitmap=getBitmap(); freeBitmap(); return bitmap; } public void freeBitmap() { if(_handler==null) return; jniFreeBitmapData(_handler); _handler=null; } @Override protected void finalize() throws Throwable { super.finalize(); if(_handler==null) return; Log.w("DEBUG","JNI bitmap wasn't freed nicely.please rememeber to free the bitmap as soon as you can"); freeBitmap(); } } 

Android.mk – el archivo de propiedades de JNI:

 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := JniBitmapStorageTest LOCAL_SRC_FILES := JniBitmapStorageTest.cpp LOCAL_LDLIBS := -llog LOCAL_LDFLAGS += -ljnigraphics include $(BUILD_SHARED_LIBRARY) APP_OPTIM := debug LOCAL_CFLAGS := -g 

JniBitmapStorageTest.cpp: el material “mágico” va aquí:

 #include  #include  #include  #include  #include  #include  #include  #define LOG_TAG "DEBUG" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) extern "C" { JNIEXPORT jobject JNICALL Java_com_example_jnibitmapstoragetest_JniBitmapHolder_jniStoreBitmapData(JNIEnv * env, jobject obj, jobject bitmap); JNIEXPORT jobject JNICALL Java_com_example_jnibitmapstoragetest_JniBitmapHolder_jniGetBitmapFromStoredBitmapData(JNIEnv * env, jobject obj, jobject handle); JNIEXPORT void JNICALL Java_com_example_jnibitmapstoragetest_JniBitmapHolder_jniFreeBitmapData(JNIEnv * env, jobject obj, jobject handle); } class JniBitmap { public: uint32_t* _storedBitmapPixels; AndroidBitmapInfo _bitmapInfo; JniBitmap() { _storedBitmapPixels = NULL; } }; JNIEXPORT void JNICALL Java_com_example_jnibitmapstoragetest_JniBitmapHolder_jniFreeBitmapData(JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap->_storedBitmapPixels == NULL) return; delete[] jniBitmap->_storedBitmapPixels; jniBitmap->_storedBitmapPixels = NULL; delete jniBitmap; } JNIEXPORT jobject JNICALL Java_com_example_jnibitmapstoragetest_JniBitmapHolder_jniGetBitmapFromStoredBitmapData(JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap->_storedBitmapPixels == NULL) { LOGD("no bitmap data was stored. returning null..."); return NULL; } // //creating a new bitmap to put the pixels into it - using Bitmap Bitmap.createBitmap (int width, int height, Bitmap.Config config) : // //LOGD("creating new bitmap..."); jclass bitmapCls = env->FindClass("android/graphics/Bitmap"); jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); jstring configName = env->NewStringUTF("ARGB_8888"); jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config"); jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(bitmapConfigClass, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"); jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass, valueOfBitmapConfigFunction, configName); jobject newBitmap = env->CallStaticObjectMethod(bitmapCls, createBitmapFunction, jniBitmap->_bitmapInfo.height, jniBitmap->_bitmapInfo.width, bitmapConfig); // // putting the pixels into the new bitmap: // int ret; void* bitmapPixels; if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &bitmapPixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); return NULL; } uint32_t* newBitmapPixels = (uint32_t*) bitmapPixels; int pixelsCount = jniBitmap->_bitmapInfo.height * jniBitmap->_bitmapInfo.width; memcpy(newBitmapPixels, jniBitmap->_storedBitmapPixels, sizeof(uint32_t) * pixelsCount); AndroidBitmap_unlockPixels(env, newBitmap); //LOGD("returning the new bitmap"); return newBitmap; } JNIEXPORT jobject JNICALL Java_com_example_jnibitmapstoragetest_JniBitmapHolder_jniStoreBitmapData(JNIEnv * env, jobject obj, jobject bitmap) { AndroidBitmapInfo bitmapInfo; uint32_t* storedBitmapPixels = NULL; //LOGD("reading bitmap info..."); int ret; if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) { LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); return NULL; } LOGD("width:%d height:%d stride:%d", bitmapInfo.width, bitmapInfo.height, bitmapInfo.stride); if (bitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { LOGE("Bitmap format is not RGBA_8888!"); return NULL; } // //read pixels of bitmap into native memory : // //LOGD("reading bitmap pixels..."); void* bitmapPixels; if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); return NULL; } uint32_t* src = (uint32_t*) bitmapPixels; storedBitmapPixels = new uint32_t[bitmapInfo.height * bitmapInfo.width]; int pixelsCount = bitmapInfo.height * bitmapInfo.width; memcpy(storedBitmapPixels, src, sizeof(uint32_t) * pixelsCount); AndroidBitmap_unlockPixels(env, bitmap); JniBitmap *jniBitmap = new JniBitmap(); jniBitmap->_bitmapInfo = bitmapInfo; jniBitmap->_storedBitmapPixels = storedBitmapPixels; return env->NewDirectByteBuffer(jniBitmap, 0); } 

Si solo desea almacenar mapas de bits fuera del montón, una solución más simple es usar memoria de paquetes.

Esta es la esencia de esto (código completo a continuación). Puede usarlo para instancias Parcelable no sean Bitmap . Úselo así:

 private final CachedParcelable cache = new CachedParcelable<>(Bitmap.CREATOR); cache.put(bitmap); bitmap = cache.get(); cache.close(); 

 public final class CachedParcelable implements AutoCloseable { private final Parcelable.Creator creator; private Parcel cache; public CachedParcelable(Parcelable.Creator creator) { this.creator = creator; } public synchronized T get() { if (cache == null) return null; try { cache.setDataPosition(0); return creator.createFromParcel(cache); } catch (BadParcelableException e) { // } catch (RuntimeException e) { if (creator != Bitmap.CREATOR) throw e; } return null; } public synchronized void put(T value) { if (cache != null) cache.recycle(); if (value == null) { cache = null; return; } try { cache = Parcel.obtain(); value.writeToParcel(cache, 0); } catch (RuntimeException e) { if (creator != Bitmap.CREATOR) throw e; } } @Override public synchronized void close() { if (cache != null) { cache.recycle(); cache = null; } } }