web-dev-qa-db-ja.com

ビットマップをネイティブメモリにキャッシュする方法

10,000ポイントについて、このクールなWebサイトで何かを構成することにしました。ネイティブメモリにビットマップをキャッシュするメカニズムです。

バックグラウンド

Androidデバイスの各アプリのメモリ量は非常に限られています。ヒープの範囲は16 MB〜128 MBで、 さまざまなパラメーター

この制限を超えるとOOMが発生し、ビットマップを使用するときに何度も発生する可能性があります。

多くの場合、アプリはこれらの制限を克服し、巨大なビットマップで重い操作を実行するか、後で使用するためにそれらを保存する必要があるかもしれません。

私が思いついたのは、単純なJavaクラスで、これらの目的のために物事が少し簡単になります。

ビットマップデータを保存し、必要なときに復元できるようにするために、JNIを使​​用しています。

クラスの複数のインスタンスをサポートするために、見つけたトリック( here )を使用する必要がありました。

重要な注意事項

  • データは引き続きRAMに保存されるため、デバイスに十分なRAMがない場合、アプリが強制終了される可能性があります。

  • できるだけ早くメモリを解放してください。メモリリークを回避するだけでなく、アプリがバックグラウンドになったときに、システムによって優先順位が優先されて最初に強制終了されることを回避するためでもあります。

  • メモリの解放を忘れたくない場合は、ビットマップを復元するたびにメモリを解放するか、クラスに Closable

  • 安全対策として、finalize()メソッドでネイティブメモリを自動的に解放しましたが、ジョブの責任を負わせません。リスクが高すぎます。また、このようなことが発生したときにログに書き込むようにしました。

  • 動作方法は、データ全体をJNIオブジェクトにコピーすることです。復元するために、ビットマップを最初から作成し、データを内部に配置します。

  • 使用および復元されるビットマップはARGB_8888形式です。もちろん、あなたはあなたが望むものにそれを変えることができます、ただコードを変えることを忘れないでください...

  • 大きなビットマップは、保存と復元に時間がかかる可能性があるため、バックグラウンドスレッドで行うのが賢明かもしれません。

  • これは完全なOOMソリューションではありませんが、役立つ可能性があります。たとえば、キャッシュ自体にヒープメモリを使用せずに、独自のLruCacheと組み合わせて使用​​できます。

  • コードは保存および復元専用です。何らかの操作を実行する必要がある場合は、いくつかの調査を実行する必要があります。 openCV が答えかもしれませんが、基本的なものを実行したい場合は、自分で実装できます(例はこちらJNIを使​​用した回転可能な大きな画像)。他の選択肢を知っているなら、 here を教えてください。

これが一部の人々に役立つことを願っています。コメントを書き留めてください。

また、コードの問題や改善の提案を見つけた場合はお知らせください。


より良いソリューション

JNI側でさらに多くの操作を実行する場合は、 this post を使用できます。ここで書いたコードに基づいていますが、より多くの操作を実行でき、独自のコードを簡単に追加できます。

38

説明

サンプルコードは、2つの異なるビットマップ(小さなビットマップですが、単なるデモです)を保存し、元のJava onesをリサイクルし、後でそれらをJava =インスタンスとそれらを使用します。

ご想像のとおり、レイアウトには2つのimageViewがあります。それは非常に明白なので、私はコードにそれを含めませんでした。

必要に応じて、コードを独自のパッケージに変更することを忘れないでください。変更しないと動作しません。

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

ヒープからビットマップをキャッシュするだけの場合、より簡単な解決策は、パーセルメモリを使用することです。

これは Gist です(以下のコード全体)。 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;
        }
    }
}
1
Nuno Cruces