如何将位图caching到本机内存

对于我的10,000点,我已经决定与这个酷酷的网站相提并论:一种机制来caching本地内存上的位图。

背景

Android设备的每个应用程序的内存数量非常有限 – 堆栈范围从16MB到128MB,具体取决于各种参数

如果你通过这个限制,你会得到OOM,这在使用位图的时候会发生很多次。

很多时候,一个应用程序可能需要克服这些限制,在庞大的位图上执行繁重的操作,或者只是将它们存储起来供以后使用,而且您需要

我想到的是一个简单的java类,它可以使事情变得更容易。

它使用JNI来存储位图数据,并能够在需要时进行恢复。

为了支持这个类的多个实例,我不得不使用我发现的一个技巧( 这里 )。

重要笔记

  • 数据仍然存储在RAM中,所以如果设备没有足够的RAM,应用程序可能会被终止。

  • 请记住尽快释放内存。 这不仅是为了避免内存泄漏,而且也是为了避免系统优先被杀死,一旦你的应用程序进入后台。

  • 如果不想忘记释放内存,则可以在每次还原位图时释放它,或者使类实现可closures

  • 作为一个安全措施,我已经使它在finalize()方法中自动释放它的本地内存,但是不要让它对这个工作负责。 这太危险了。 当这种事情发生的时候,我也把它写入日志。

  • 它的工作方式是将整个数据复制到JNI对象中,为了恢复,它从头开始创build位图并将数据放入其中。

  • 正在使用和恢复的位图是ARGB_8888格式。 当然,你可以改变它到任何你想要的,只是不要忘记改变代码…

  • 大的位图可能需要一些时间来存储和恢复,所以在后台线程上执行它可能是明智的。

  • 这不是一个完整的OOM解决scheme,但它可以帮助。 例如,您可以将其与您自己的LruCache一起使用,同时避免将caching本身用于堆内存。

  • 代码仅用于存储和恢复。 如果你需要执行一些操作,你将需要进行一些研究。 openCV可能是答案,但如果你想执行一些基本的东西,你可以自己实现它们( 这里是一个使用JNI旋转大图片的例子 )。 如果你知道其他的select,请在这里告诉我。

希望这对一些人有用。 请写下您的意见。

另外,如果您发现代码有任何问题或意见不清,请告诉我。


解决scheme更好

如果你想在JNI方面执行更多的操作,你可以使用我所做的这篇文章 。 它基于我在这里编写的代码,但允许您执行更多操作,并且可以轻松添加更多自己的代码。

说明

示例代码展示了如何存储2个不同的位图(小的,但它只是一个演示),回收原始的Java,然后将其恢复到Java实例并使用它们。

你可能会猜到,这个布局有2个imageViews。 我没有把它包含在代码中,因为它很明显。

如果需要的话,请记得将代码更改为您自己的包,否则事情将无法正常工作。

MainActivity.java – 如何使用:

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 – JNI和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 – 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 – “神奇”的东西在这里:

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