如何在使用大图像时使用JNI位图操作来帮助避免OOM?背景大多数时候,在Android上获取OOM是由于使用了太多位图和/或创建大位图。最近我决定试用JNI,以便通过将数据本身存储在JNI端来避免OOM。在与JNI搞砸了一段时间后,我在SO上创建了一些帖子,寻求帮助并分享我的知识,现在我决定与你分享更多代码。如果有人有兴趣阅读调查结果或做出贡献,这里有帖子:如何将位图缓存到本机内存中在Android上使用JNI进行图像解码和操作JNI - 如何使用具有不同字段的多个Jni包装器实例?使用JNI和NDK旋转位图这一次,我添加了存储,恢复,裁剪和旋转位图的功能。它应该很容易添加更多选项,如果其他人在这里将自己的代码添加到更有用的功能,我会很高兴。所以我要展示的代码实际上是合并了我创造的所有东西。示例使用代码: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上找到。随时提供建议和贡献。重要笔记与此处显示的相同的注释,加上:此处编写的当前功能(在项目页面上更新):商店恢复逆时针旋转90度作物。我为这段代码采用的方法是内存效率(仅使用我需要的内存,并在不需要时释放它)和CPU效率(我尽可能使用指针和CPU内存缓存优化)。为了获得最佳性能,我做了很少的验证,特别是在JNI部分。最好管理java“world”上的验证。我认为应该添加许多缺失的功能,我希望我有时间添加它们。如果有人愿意贡献,我也很乐意添加他们的代码。以下是我认为可能有用的功能:获取当前位图信息缩放位图,包括选择使用哪种算法(最近邻和双线性插值应该足够)。使用不同的位图格式在JNI中进行解码,以避免从一开始就创建java位图(而不是在java世界中使用堆),只是在完成所有操作时才结束。面部检测任何角度的旋转,或至少是明显的旋转。目前我只增加了90度逆时针旋转。
1 回答
白衣非少年
TA贡献1155条经验 获得超0个赞
注意:这是一个有点旧的代码。对于最新的,请查看github上的项目页面。
JNI / Android.mk
LOCAL_PATH := $(call my-dir)#bitmap operations moduleinclude $(CLEAR_VARS)LOCAL_MODULE := JniBitmapOperationsLOCAL_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; // A.D D.C // ...>... // B.C A.B 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(); } }
- 1 回答
- 0 关注
- 375 浏览
添加回答
举报
0/150
提交
取消