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; } }}