JNI位图操作,帮助避免OOM使用大图像时 [英] JNI bitmap operations , for helping to avoid OOM when using large images

查看:515
本文介绍了JNI位图操作,帮助避免OOM使用大图像时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

大部分的时间,让OOM在Android的原因是使用了太多的位图和/或创建大位图。

most of the times, getting OOM on android is due to using too many bitmaps and/or creating large bitmaps.

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

recently i've decided to try out JNI in order to allow avoiding OOM by storing the data itself on the JNI side.

瞎搞用JNI了一段时间后,我已经创建的SO寻求帮助和分享我的知识一些帖子,我现在已经决定与大家分享一些更多的code。这里如果有人有兴趣阅读的研究结果或有助于职位:

after messing around with JNI for a while, i've created some posts on SO asking for help and sharing my knowledge, and i've now decided to share some more code with you. here are the posts in case anyone is interested in reading the findings or contributing :

图像处理及操控性上的Andr​​oid 的JNI侧

JNI - 如何使用多个JNI包装实例与不同领域

旋转使用JNI和放大器的位图; NDK

这个时候,我已经添加到存储,恢复,剪切,旋转位图的能力。它应该很容易添加更多选项和我会很高兴,如果这里的其他人会自己加code到更多有用的功能

this time, i've added the ability to store,restore, crop and rotate bitmaps. it should be easy to add more options and I would be happy if other people here would add their own code to more useful functions .

因此​​code我要告诉实际上是融合了所有我所创建的东西。

so the code i'm about to show is actually merging of all the things i've created.

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 此处

    The project is available on github

    • project page is available on github here .

      随时给建议和贡献。

      同样的音符如 此处 ,再加上:

      same notes as shown here, plus:

      • 这是在这里写电流的特性(更多更新的项目页面上):

      • current features that are written here (more updated on the project page) :

      • 存储

      • store

      恢复

      旋转90度逆时针

      作物。

      我已经采取了这种code的做法既是记忆的效率(仅使用​​,我需要的内存,并释放它不需要时),和CPU的效率(我试图用指针和CPU内存高速缓存优化只要有可能)。

      the approach i've taken for this code is both memory efficiency (use only memory that i need, and free it when not needed), and CPU efficiency (i tried to use pointers and CPU memory cache optimizations whenever possible).

      为了获得最佳性能,我所做甚少验证,特别是在JNI的一部分。它可能是最好的管理Java的世界的验证。

      for best performance, i've done really few validations, especially on the JNI part. it might be best to manage the validations on the java "world".

      还是有很多的丢失功能,我认为应该加,我希望我能有时间来添加。如果有人愿意做出贡献,我会很乐意加入他们code了。这里,我想可能是有用的功能:

      there are still many missing features that i think should be added, and i hope that i will have the time to add them . if anyone wishes to contribute, i will be glad to add they code too. here are the functions that i think could be useful:

      • 获取当前的位图信息

      • get current bitmap info

      规模的位图,其中包括选择要使用的算法(最近邻和双线性插值应该是足够了)。

      scale bitmaps, including choice of which algorithm to use (nearest neighbour and bilinear interpolation should be enough).

      使用不同的位图格式

      做内JNI解码,以避免创建了Java位图(而不是使用堆在Java世界),从一开始,只是到了最后,当所有的操作完成。

      do the decoding within JNI, to avoid creation of the java bitmap (and not use the heap on the java world) from the beginning, only at the end, when you finished with all of the operations.

      人脸检测

      旋转在任意角度,或者至少是明显的。目前我只加了90度,反时​​针旋转。

      rotation in any angle, or at least the obvious ones . currently i only added rotation of 90 degrees counter clock wise .

      推荐答案

      注:这是一个有点老了code。对于最新的,检查出<一个项目页面href="http://github.com/AndroidDeveloperLB/AndroidJniBitmapOperations">github.

      NOTE: this is a bit old code. for the most updated one, check out the project page on github.

      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;
        // 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();
          }
        }
      

      这篇关于JNI位图操作,帮助避免OOM使用大图像时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆