Как кешировать растровые изображения в родную память

За свои 10000 баллов я решил кое-что добавить с этим классным сайтом: механизмом для кэширования растровых изображений на родной памяти.

Фон

Устройства Android имеют очень ограниченный объем памяти для каждого приложения - размер кучи варьируется от 16 до 128 МБ, в зависимости от различных параметров.

Если вы превысите этот предел, вы получите OOM, и это может происходить много раз, когда вы используете растровые изображения.

Во многих случаях приложению может потребоваться преодолеть эти ограничения, выполнить тяжелые операции с огромными растровыми изображениями или просто сохранить их для дальнейшего использования, и вам необходимо

Я пришел к выводу, что это простой Java-класс, который немного облегчит эти задачи.

Он использует JNI для хранения растровых данных и возможности их восстановления при необходимости.

Чтобы поддерживать несколько экземпляров класса, мне пришлось использовать трюк, который я нашел ( здесь) .

Важные заметки

  • Данные по-прежнему хранятся в оперативной памяти, поэтому, если на устройстве недостаточно оперативной памяти, приложение может быть уничтожено.

  • Не забудьте освободить память как можно скорее. это не только для предотвращения утечек памяти, но и для того, чтобы система не была приоритетной для того, чтобы система была убита первой, когда ваше приложение выходит на задний план.

  • Если вы не хотите забывать об освобождении памяти, вы можете освободить ее каждый раз, когда восстанавливаете растровое изображение, или заставить класс реализовать Closable.

  • В качестве меры безопасности я сделал так, что он автоматически освобождает собственную память в методе finalize(), но не позволяю ему нести ответственность за работу. это слишком рискованно я также сделал это записать в журнал, когда такая вещь происходит.

  • Это работает путем копирования всех данных в объекты JNI, а для восстановления он создает растровое изображение с нуля и помещает данные внутрь.

  • Используемые и восстановленные растровые изображения имеют формат ARGB_8888. Конечно, вы можете изменить его на любое другое, просто не забудьте изменить код...

  • Большие растровые изображения могут занимать время для хранения и восстановления, поэтому было бы разумно сделать это в фоновом потоке.

  • Это не полное решение OOM, но оно может помочь. например, вы можете использовать его вместе с вашим собственным LruCache, избегая использования кучи памяти для самого кэша.

  • Код предназначен только для хранения и восстановления. Если вам нужно выполнить некоторые операции, вам нужно будет провести некоторые исследования. openCV может быть ответом, но если вы хотите выполнить некоторые базовые вещи, вы можете реализовать их самостоятельно ( вот пример поворота больших изображений с использованием JNI). если вы знаете другие варианты, пожалуйста, дайте мне знать, здесь.

Надеюсь, что это будет полезно для некоторых людей. пожалуйста, запишите ваши комментарии.

Кроме того, если вы обнаружите какие-либо проблемы с кодом или предложением для улучшения, пожалуйста, дайте мне знать.


Лучшее решение

Если вы хотите выполнить еще больше операций на стороне JNI, вы можете использовать этот пост, который я сделал. он основан на коде, который я написал здесь, но позволяет вам выполнять больше операций, и вы можете легко добавить больше своих собственных.

2 ответа

Решение

объяснение

В примере кода показано, как хранить 2 разных растровых изображения (маленькие, но только демонстрационные), перерабатывать исходные java, а затем восстанавливать их в экземпляры java и использовать их.

как вы можете догадаться, макет имеет 2 изображения. я не включил это в код, так как это совершенно очевидно.

не забудьте поменять код на ваш собственный пакет, если вам нужно, иначе ничего не получится.

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

Если вы просто хотите кэшировать растровые изображения из кучи, более простым решением является использование памяти участков.

Это суть этого (полный код ниже). Вы можете использовать его для Parcelable кроме случаев Bitmap, Используйте это так:

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;
        }
    }
}
Другие вопросы по тегам