JNI位图操作,帮助避免使用大图像时的OOM

背景

大多数情况下,在Android上获得OOM是由于使用了太多的位图和/或创build大的位图。

最近我决定尝试JNI,以便通过将数据本身存储在JNI端来避免OOM。

经过一段时间的搞乱JNI后,我已经创build了一些post寻求帮助和分享我的知识,我现在决定与你分享一些更多的代码。 这里是任何人有兴趣阅读发现或贡献的post:

  • 如何将位图caching到本机内存

  • 在android上使用JNI进行图像解码和操作

  • JNI – 如何使用不同字段的多个Jni包装实例?

  • 使用JNI和NDK旋转位图

这一次,我添加了存储,恢复,裁剪和旋转位图的function。 应该很容易添加更多的选项, 如果其他人在这里将他们自己的代码添加到更有用的function我会很高兴

所以我要展示的代码实际上是我创build的所有东西的合并。

使用示例代码:

Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher); final int width=bitmap.getWidth(),height=bitmap.getHeight(); // store the bitmap in the JNI "world" final JniBitmapHolder bitmapHolder=new JniBitmapHolder(bitmap); // no need for the bitmap on the java "world", since the operations are done on the JNI "world" bitmap.recycle(); // crop a center square from the bitmap, from (0.25,0.25) to (0.75,0.75) of the bitmap. bitmapHolder.cropBitmap(width/4,height/4,width*3/4,height*3/4); //rotate the bitmap: bitmapHolder.rotateBitmapCcw90(); //get the output java bitmap , and free the one on the JNI "world" bitmap=bitmapHolder.getBitmapAndFree(); 

该项目在github上可用

  • 项目页面在github上可用。

  • 随时给予build议和贡献。

重要笔记

相同的音符如下所示,再加上:

  • 当前写在这里的function(在项目页面上更新):

    • 商店

    • 恢复

    • 逆时针旋转90度

    • 作物。

  • 我为这个代码采取的方法是内存效率(只使用我需要的内存,并在不需要时释放它)和CPU效率(我试图尽可能使用指针和CPU内存caching优化)。

  • 为了获得最佳性能,我做了很less的validation,特别是在JNI部分。 在java“世界”上pipe理validation可能是最好的。

  • 还有很多我认为应该添加的缺失function,我希望我有时间添加它们。 如果有人愿意贡献,我也很乐意添加他们的代码。 这里是我认为可能有用的function:

    • 获取当前的位图信息

    • 包括select使用哪种algorithm(最近邻和双线性插值应该是足够的)。

    • 使用不同的位图格式

    • 在JNI中进行解码,以避免从一开始就创buildjava位图(而不是在java世界中使用堆),只在最后完成所有操作。

    • 面部检测

    • 任何angular度的旋转,或至less是明显的旋转。 目前,我只增加了90度逆时针旋转。

注意:这是一个旧的代码。 为最新的一个,检查在github上的项目页面。

JNI / Android.mk

 LOCAL_PATH := $(call my-dir) #bitmap operations module include $(CLEAR_VARS) LOCAL_MODULE := JniBitmapOperations LOCAL_SRC_FILES := JniBitmapOperations.cpp LOCAL_LDLIBS := -llog LOCAL_LDFLAGS += -ljnigraphics include $(BUILD_SHARED_LIBRARY) APP_OPTIM := debug LOCAL_CFLAGS := -g #if you need to add more module, do the same as the one we started with (the one with the CLEAR_VARS) 

JNI / JniBitmapOperations.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_jni_bitmap_1operations_JniBitmapHolder_jniStoreBitmapData(JNIEnv * env, jobject obj, jobject bitmap); JNIEXPORT jobject JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniGetBitmapFromStoredBitmapData(JNIEnv * env, jobject obj, jobject handle); JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniFreeBitmapData(JNIEnv * env, jobject obj, jobject handle); JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniRotateBitmapCcw90(JNIEnv * env, jobject obj, jobject handle); JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniCropBitmap(JNIEnv * env, jobject obj, jobject handle, uint32_t left, uint32_t top, uint32_t right, uint32_t bottom); } class JniBitmap { public: uint32_t* _storedBitmapPixels; AndroidBitmapInfo _bitmapInfo; JniBitmap() { _storedBitmapPixels = NULL; } }; /**crops the bitmap within to be smaller. note that no validations are done*/ // JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniCropBitmap(JNIEnv * env, jobject obj, jobject handle, uint32_t left, uint32_t top, uint32_t right, uint32_t bottom) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* previousData = jniBitmap->_storedBitmapPixels; uint32_t oldWidth = jniBitmap->_bitmapInfo.width; uint32_t newWidth = right - left, newHeight = bottom - top; uint32_t* newBitmapPixels = new uint32_t[newWidth * newHeight]; uint32_t* whereToGet = previousData + left + top * oldWidth; uint32_t* whereToPut = newBitmapPixels; for (int y = top; y < bottom; ++y) { memcpy(whereToPut, whereToGet, sizeof(uint32_t) * newWidth); whereToGet += oldWidth; whereToPut += newWidth; } //done copying , so replace old data with new one delete[] previousData; jniBitmap->_storedBitmapPixels = newBitmapPixels; jniBitmap->_bitmapInfo.width = newWidth; jniBitmap->_bitmapInfo.height = newHeight; } /**rotates the inner bitmap data by 90 degress counter clock wise*/ // JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniRotateBitmapCcw90(JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* previousData = jniBitmap->_storedBitmapPixels; AndroidBitmapInfo bitmapInfo = jniBitmap->_bitmapInfo; uint32_t* newBitmapPixels = new uint32_t[bitmapInfo.height * bitmapInfo.width]; int whereToPut = 0; // AD DC // ...>... // BC AB for (int x = bitmapInfo.width - 1; x >= 0; --x) for (int y = 0; y < bitmapInfo.height; ++y) { uint32_t pixel = previousData[bitmapInfo.width * y + x]; newBitmapPixels[whereToPut++] = pixel; } delete[] previousData; jniBitmap->_storedBitmapPixels = newBitmapPixels; uint32_t temp = bitmapInfo.width; bitmapInfo.width = bitmapInfo.height; bitmapInfo.height = temp; } /**free bitmap*/ // JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_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; } /**restore java bitmap (from JNI data)*/ // JNIEXPORT jobject JNICALL Java_com_jni_bitmap_1operations_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.width, jniBitmap->_bitmapInfo.height, 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; } /**store java bitmap as JNI data*/ // JNIEXPORT jobject JNICALL Java_com_jni_bitmap_1operations_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); } 

SRC / COM / JNI / bitmap_operations / JniBitmapHolder.java

 package com.jni.bitmap_operations; import java.nio.ByteBuffer; import android.graphics.Bitmap; import android.util.Log; public class JniBitmapHolder { ByteBuffer _handler =null; static { System.loadLibrary("JniBitmapOperations"); } private native ByteBuffer jniStoreBitmapData(Bitmap bitmap); private native Bitmap jniGetBitmapFromStoredBitmapData(ByteBuffer handler); private native void jniFreeBitmapData(ByteBuffer handler); private native void jniRotateBitmapCcw90(ByteBuffer handler); private native void jniCropBitmap(ByteBuffer handler,final int left,final int top,final int right,final int bottom); public JniBitmapHolder() {} public JniBitmapHolder(final Bitmap bitmap) { storeBitmap(bitmap); } public void storeBitmap(final Bitmap bitmap) { if(_handler!=null) freeBitmap(); _handler=jniStoreBitmapData(bitmap); } public void rotateBitmapCcw90() { if(_handler==null) return; jniRotateBitmapCcw90(_handler); } public void cropBitmap(final int left,final int top,final int right,final int bottom) { if(_handler==null) return; jniCropBitmap(_handler,left,top,right,bottom); } 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(); } }