39

对于我的 10,000 分,我决定为这个很酷的网站贡献一些东西:一种在本机内存上缓存位图的机制。

背景

Android 设备的每个应用程序的内存量非常有限 - 堆的范围从 16MB 到 128MB,具体取决于各种参数

如果超过了这个限制,你就会得到 OOM ,当你使用位图时,这可能会发生很多次。

很多时候,一个应用程序可能需要克服这些限制,对巨大的位图执行繁重的操作,或者只是将它们存储起来以备后用,而您需要

我想出的是一个简单的java类,它可以让事情变得更容易实现这些目的。

它使用 JNI 来存储位图数据,并能够在需要时恢复它。

为了支持该类的多个实例,我不得不使用我发现的一个技巧(这里)。

重要笔记

  • 数据仍然存储在 RAM 中,因此如果设备没有足够的 RAM,应用程序可能会被终止。

  • 记得尽快释放内存。这不仅是为了避免内存泄漏,而且也是为了避免在您的应用程序进入后台时被系统优先杀死。

  • 如果你不想忘记释放内存,你可以在每次恢复位图时释放它,或者让类实现Closable

  • 作为一项安全措施,我已经让它在 finalize() 方法中自动释放其本机内存,但不要让它负责这项工作。这太冒险了。发生这种情况时,我也将其写入日志。

  • 它的工作方式是将整个数据复制到 JNI 对象中,为了恢复,它从头开始创建位图并将数据放入其中。

  • 正在使用和恢复的位图采用 ARGB_8888 格式。当然,您可以将其更改为您想要的任何内容,只是不要忘记更改代码...

  • 大型位图可能需要时间来存储和恢复,因此在后台线程上执行此操作可能是明智的。

  • 这不是一个完整的 OOM 解决方案,但它可以提供帮助。例如,您可以将它与您自己的 LruCache 结合使用,同时避免将堆内存用于缓存本身。

  • 代码仅用于存储和恢复。如果您需要执行一些操作,则需要进行一些研究。openCV可能是答案,但如果您希望执行一些基本操作,您可以自己实现它们(这里是使用 JNI 旋转大图像的示例)。如果您知道其他替代方案,请在此处告诉我。

希望这对某些人有用。请写下您的意见。

另外,如果您发现代码有任何问题或改进建议,请告诉我。


更好的解决方案

如果您希望在 JNI 端执行更多操作,可以使用我发布的这篇文章。它基于我在这里编写的代码,但允许您执行更多操作,并且您可以轻松添加更多自己的操作。

4

2 回答 2

15

解释

示例代码展示了如何存储 2 个不同的位图(小的,但它只是一个演示),回收原始的 java 的,然后将它们恢复到 java 实例并使用它们。

正如您可能猜到的那样,该布局有 2 个 imageViews。我没有将它包含在代码中,因为它很明显。

如果需要,请记住将代码更改为您自己的包,否则将无法正常工作。

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);
  }
于 2013-07-27T17:44:39.917 回答
2

如果您只想从堆中缓存位图,更简单的解决方案是使用包裹内存。

这是它的要点(下面的完整代码)。您可以将它用于ParcelableBitmap. 像这样使用它:

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;
        }
    }
}
于 2018-06-07T10:12:53.640 回答