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

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

Задний план

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

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

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

То, что я придумал, - это простой класс Java, который немного упростит задачу для этих целей.

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

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

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

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

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


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

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


person android developer    schedule 27.07.2013    source источник
comment
@Akanksha Вы пробовали образец? это сработало? если это так, преобразуйте проект, который я сделал, в библиотеку Android, а затем используйте его из исходного проекта. если это все еще не работает, скопируйте весь код в свой проект. я не против :) Кстати, как мне добавить информацию о лицензии (Apache)?   -  person android developer    schedule 01.01.2014
comment
Спасибо! можете ли вы поделиться, кроме libs, jni и src, иначе я должен скопировать в свой проект, чтобы использовать его.   -  person Akanksha Rathore    schedule 01.01.2014
comment
@Akanksha, ты можешь делать все, что хочешь, это твое приложение...   -  person android developer    schedule 01.01.2014
comment
я прошу процедуру интеграции с моим приложением.   -  person Akanksha Rathore    schedule 01.01.2014
comment
@Akanksha Хотите, чтобы я сделал пример проекта, в котором используется мой проект? Может быть, так будет лучше, чтобы я отделил сэмпл от библиотеки, таким образом сделав библиотеку минимальным количеством файлов.   -  person android developer    schedule 05.01.2014
comment
Ты немного опоздал. Я уже сделал свою работу с другим подходом, если ты сможешь это сделать, это будет полезно для тех, кто не знаком с ndk, чтобы люди могли добавлять его как библиотеку и выполнять свою работу. Когда вы закончите, пожалуйста, обновите ссылку. Спасибо.   -  person Akanksha Rathore    schedule 06.01.2014
comment
@Akanksha хорошо, я отделил библиотеку от образца. теперь он должен быть намного чище. Спасибо за совет. вы можете проверить его и убедиться, что он работает нормально: github.com/AndroidDeveloperLB/AndroidJniBitmapOperations   -  person android developer    schedule 06.01.2014


Ответы (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);
  }
person android developer    schedule 27.07.2013
comment
Да, проект github работает нормально. поэтому я хочу включить его в свой проект, и я попробовал оба способа преобразовать образец проекта в библиотеку и в тег Android, я включил его. он не показывает никаких ошибок во время компиляции, но во время выполнения ExceptionInInitializerError. Я думаю, проблема здесь System.loadLibrary(JniBitmapOperations); или может быть проблема с именем пакета. Я никогда не использовал ndk. Как это реализовать? - person Akanksha Rathore; 01.01.2014
comment
Вам нужно будет сделать некоторые исследования по этому вопросу. пожалуйста, продолжайте комментарии там, где вы начали, а не в нескольких местах. однако вы правы в том, что пакет важен, поскольку код на стороне C/C++ предполагает их пути. - person android developer; 01.01.2014
comment
Спасибо за ответ. Что мне нужно изменить, если я сделаю ваш проект проектом библиотеки и включу его в свое приложение. как имя пакета или путь? - person Akanksha Rathore; 01.01.2014
comment
вы впервые импортируете библиотеку Android или у вас есть конкретная проблема именно с этой? если есть проблема с этим, вы можете просто скопировать его файлы в свой проект, изменить пути пакетов во всех файлах (особенно файлы C/C++), и все должно работать нормально. - person android developer; 01.01.2014
comment
зависит от того, что вы с ним делаете (в какой пакет вы его положили). - person android developer; 01.01.2014
comment
@androiddeveloper После сохранения изображения в собственной памяти, когда я пытаюсь получить изображение, а затем сохранить его на SD-карту, оно возвращает мне черное изображение без отображения вообще. любая подсказка об этом? - person AndroidLearner; 08.04.2014
comment
вам нужно сначала передать ширину в методе создания растрового изображения, подобно этому jobject newBitmap = env-›CallStaticObjectMethod(bitmapCls, createBitmapFunction, jniBitmap-›_bitmapInfo.width, jniBitmap-›_bitmapInfo.height, bitmapConfig); - person Sabish.M; 03.10.2016
comment
@AndroidLearner Вы пробовали образец? Он должен работать нормально. - person android developer; 03.10.2016

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

Это суть (полный код ниже). Вы можете использовать его для 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;
        }
    }
}
person Nuno Cruces    schedule 07.06.2018
comment
Парцеллирование растрового изображения уберет его из кучи? - person android developer; 07.06.2018
comment
Да, Parcel использует родную кучу malloc. - person Nuno Cruces; 08.06.2018
comment
Интересный. Но можете ли вы что-нибудь сделать с растровым изображением, когда оно есть, как в моем решении JNI? Или просто кешировать? - person android developer; 08.06.2018
comment
Нет, он непрозрачный. Но если вашей единственной целью является хранение (несколько) больших растровых изображений в кэше вне кучи, это самое простое решение, которое я знаю. - person Nuno Cruces; 08.06.2018
comment
Что вы имеете ввиду под непрозрачностью? И есть ли способ узнать, сколько растровых изображений я могу хранить? Может быть, получить общий объем оперативной памяти, используемой моим приложением (включая все виды), по сравнению с тем, что для него бесплатно (очевидно, это не вся свободная оперативная память устройства ...)? - person android developer; 08.06.2018
comment
Я имею в виду, что нет никакого способа получить доступ к данным, пока они разделены. Я не уверен, сколько. Я использую его для кэширования пары действительно больших растровых изображений. - person Nuno Cruces; 08.06.2018
comment
Я понимаю. Это тоже приятно знать. Я даю вам +1 за усилия. Кстати, знаете ли вы, что в Android O растровые изображения фактически не занимают места в куче памяти? - person android developer; 08.06.2018
comment
Я думаю, что все примитивные массивы больше X теперь получают собственное пространство кучи. Это то, о чем GC знает, но ему не нужно сканировать (нет ссылок на объекты). Растровые изображения используют внутри int[] , поэтому они также идут туда. - person Nuno Cruces; 08.06.2018
comment
Интересный! Где вы об этом прочитали? И почему они это сделали? Не сложнее ли теперь управлять памятью? Как я заметил, вы больше не получаете OOM при создании растровых изображений. Вы получите что-то еще: stackoverflow.com/q/48091403/878126 - person android developer; 08.06.2018
comment
Честно не помню. Еще одно интересное изменение заключается в том, что резервная память для массивов может вообще не выделяться до тех пор, пока вы не начнете чтение/запись из/в них. Кое-что из этого я выяснил, профилируя память приложения, которое загружает многомегапиксельные растровые изображения, все с кучей 10 МБ. - person Nuno Cruces; 13.06.2018
comment
Хороший. Эти решения Google кажутся мне хорошими и плохими. Хорошо, потому что гораздо меньше проблем с управлением памятью и меньше страха перед OOM. Плохо, потому что такие вещи все еще могут происходить (и всегда будут), но я не думаю, что мы можем использовать хороший способ узнать о таких вещах, и я не думаю, что у нас есть хороший API для решения этого подхода (пример: знать, сколько память, которую мы можем использовать для растровых изображений). - person android developer; 13.06.2018