How to cache bitmaps into native memory How to cache bitmaps into native memory android android

How to cache bitmaps into native memory


explanation

the sample code shows how to store 2 different bitmaps (small ones, but it's just a demo), recycle the original java ones, and later restore them to java instances and use them.

as you might guess, the layout has 2 imageViews. i didn't include it in the code since it's quite obvious.

do remember to change the code to your own package if you need, otherwise things won't work.

MainActivity.java - how to use:

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 - the "bridge" between JNI and 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 - the properties file of JNI:

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE    := JniBitmapStorageTestLOCAL_SRC_FILES := JniBitmapStorageTest.cppLOCAL_LDLIBS := -llogLOCAL_LDFLAGS += -ljnigraphicsinclude $(BUILD_SHARED_LIBRARY)APP_OPTIM := debugLOCAL_CFLAGS := -g

JniBitmapStorageTest.cpp - the "magical" stuff goes here :

#include <jni.h>#include <jni.h>#include <android/log.h>#include <stdio.h>#include <android/bitmap.h>#include <cstring>#include <unistd.h>#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);  }


If you just want to cache bitmaps off the heap, a simpler solution is to use parcel memory.

This is the Gist of it (full code bellow). You can use it for Parcelable instances other than Bitmap. Use it like this:

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

public final class CachedParcelable<T extends Parcelable> implements AutoCloseable {    private final Parcelable.Creator<T> creator;    private Parcel cache;    public CachedParcelable(Parcelable.Creator<T> 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;        }    }}