1364

ListView在每一行都有几个图像按钮。当用户单击列表行时,它会启动一个新活动。由于相机布局问题,我不得不构建自己的标签。为结果启动的活动是地图。如果我单击按钮启动图像预览(从 SD 卡加载图像),应用程序会从活动返回到ListView结果处理程序以重新启动我的新活动,这只不过是一个图像小部件。

ListView正在使用光标和 完成图像预览ListAdapter。这使它变得非常简单,但我不确定如何放置调整大小的图像(即较小的位大小而不是像素作为动态src图像按钮。所以我只是调整了从手机摄像头出来的图像的大小。

问题是OutOfMemoryError当它尝试返回并重新启动第二个活动时,我得到了一个。

  • 有没有一种方法可以轻松地逐行构建列表适配器,我可以在其中动态调整大小(按位)?

这将是可取的,因为我还需要对每行中的小部件/元素的属性进行一些更改,因为由于焦点问题,我无法使用触摸屏选择一行。(我可以用滚球。

  • 我知道我可以进行带外调整大小并保存我的图像,但这并不是我真正想要做的,但一些示例代码会很好。

一旦我禁用了图像,ListView它就会再次正常工作。

仅供参考:这就是我的做法:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS,
    DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT,
    DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong,
    R.id.gpslat, R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

R.id.imagefilename一个在哪里ButtonImage

这是我的 LogCat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

显示图像时我也有一个新错误:

22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
4

44 回答 44

907

要修复 OutOfMemory 错误,您应该执行以下操作:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

inSampleSize选项可减少内存消耗。

这是一个完整的方法。首先它读取图像大小而不解码内容本身。然后它找到最好的inSampleSize值,应该是2的幂,最后对图像进行解码。

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);

        // The new size we want to scale to
        final int REQUIRED_SIZE=70;

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE && 
              o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            scale *= 2;
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {}
    return null;
}
于 2009-05-05T09:00:14.040 回答
682

Android 培训课程“高效显示位图”为理解和处理异常 `java.lang.OutOfMemoryError: 加载位图时位图大小超出 VM 预算提供了一些重要信息。


读取位图尺寸和类型

该类BitmapFactory提供了几种解码方法(decodeByteArray()decodeFile()decodeResource()等),用于Bitmap从各种来源创建一个。根据您的图像数据源选择最合适的解码方法。这些方法试图为构造的位图分配内存,因此很容易导致OutOfMemory异常。每种类型的解码方法都有额外的签名,让您可以通过BitmapFactory.Options类指定解码选项。将inJustDecodeBounds属性设置为truewhile 解码可避免内存分配,返回null位图对象但设置outWidth,outHeightoutMimeType. 此技术允许您在构建位图(和内存分配)之前读取图像数据的尺寸和类型。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

为避免java.lang.OutOfMemory异常,请在解码之前检查位图的尺寸,除非您绝对相信来源能够为您提供大小可预测的图像数据,这些数据可以轻松地放入可用内存中。


将缩小版本加载到内存中

现在图像尺寸已知,它们可用于决定是否应将完整图像加载到内存中,或者是否应加载子采样版本。以下是一些需要考虑的因素:

  • 在内存中加载完整图像的估计内存使用量。
  • 考虑到应用程序的任何其他内存要求,您愿意承诺加载此图像的内存量。
  • 要加载图像的目标 ImageView 或 UI 组件的尺寸。
  • 当前设备的屏幕尺寸和密度。

例如,如果一个 1024x768 像素的图像最终会以 128x96 像素的缩略图显示在ImageView.

告诉解码器对图像进行二次采样,将较小的版本加载到内存中,在您的对象中设置inSampleSize为。例如,分辨率为 2048x1536 的图像使用4 进行解码会生成大约 512x384 的位图。将其加载到内存中使用 0.75MB 而不是 12MB 的完整图像(假设位图配置为)。这是一种根据目标宽度和高度计算样本大小值的方法,该值是 2 的幂:trueBitmapFactory.OptionsinSampleSizeARGB_8888

public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

注意:根据 inSampleSize文档,计算二值的幂是因为解码器通过向下舍入到最接近的二的幂来使用最终值。

要使用此方法,首先解码inJustDecodeBounds设置为true, pass the options through and then decode again using the new inSampleSize value andinJustDecodeBounds set tofalse`:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

此方法可以轻松地将任意大尺寸的位图加载到ImageView显示 100x100 像素缩略图的图像中,如以下示例代码所示:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

您可以按照类似的过程来解码来自其他来源的位图,BitmapFactory.decode*方法是根据需要替换适当的方法。

于 2012-04-12T16:31:31.110 回答
379

我对 Fedor 的代码做了一个小的改进。它基本上做同样的事情,但没有(在我看来)丑陋的 while 循环,它总是产生 2 的幂。感谢 Fedor 提出最初的解决方案,我一直被困住,直到找到他的解决方案,然后我才能做到这一点 :)

 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}
于 2010-08-23T15:25:32.137 回答
237

我来自 iOS 经验,我很沮丧地发现加载和显示图像这样基本的问题。毕竟,遇到此问题的每个人都在尝试显示尺寸合理的图像。无论如何,这是解决我的问题的两个更改(并使我的应用程序非常敏感)。

1)每次执行时BitmapFactory.decodeXYZ(),请确保传入一个BitmapFactory.Optionswith inPurgeableset to true(最好inInputShareable也设置为true)。

2) 永远不要使用Bitmap.createBitmap(width, height, Config.ARGB_8888). 我的意思是从不!几次通过后,我从来没有遇到过不会引发内存错误的事情。没有任何帮助recycle()System.gc()它总是引发异常。实际可行的另一种方法是在您的可绘制对象(或您使用上面的步骤 1 解码的另一个位图)中有一个虚拟图像,将其重新缩放为您想要的任何内容,然后操作生成的位图(例如将其传递给 Canvas为了更多的乐趣)。所以,你应该改用的是:Bitmap.createScaledBitmap(srcBitmap, width, height, false). 如果出于某种原因您必须使用蛮力创建方法,那么至少要通过Config.ARGB_4444.

这几乎可以保证为您节省数小时甚至数天。所有关于缩放图像等的讨论并没有真正起作用(除非您认为尺寸错误或图像质量下降是一种解决方案)。

于 2011-12-15T22:52:23.267 回答
96

这是一个已知的错误,不是因为大文件。由于 Android 会缓存 Drawable,因此在使用少量图像后会出现内存不足。但我找到了另一种方法,跳过android默认缓存系统。

解决方案:将图像移动到“assets”文件夹并使用以下函数获取 BitmapDrawable:

public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}
于 2011-05-24T20:17:04.343 回答
82

我遇到了同样的问题并通过避免使用 BitmapFactory.decodeStream 或 decodeFile 函数来解决它,而是使用BitmapFactory.decodeFileDescriptor

decodeFileDescriptor看起来它调用了与 decodeStream/decodeFile 不同的本机方法。

无论如何,这是有效的(请注意,我添加了一些上面的选项,但这并不是造成差异的原因。关键是调用BitmapFactory.decodeFileDescriptor而不是decodeStreamdecodeFile):

private void showImage(String path)   {

    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
    bfOptions.inTempStorage=new byte[32 * 1024]; 

    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{ 
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;                        
}

我认为 decodeStream/decodeFile 中使用的本机函数存在问题。我已经确认使用 decodeFileDescriptor 时调用了不同的本机方法。另外我读到的是“图像(位图)不是以标准 Java 方式分配的,而是通过本机调用分配的;分配是在虚拟堆之外完成的,但要 计入它!

于 2011-08-19T01:55:38.447 回答
74

我认为避免这种情况的最好方法OutOfMemoryError是面对它并理解它。

我制作了一个应用程序来故意导致OutOfMemoryError并监控内存使用情况。

在我对这个App做了很多实验之后,我得到了以下结论:

我先说一下 Honey Comb 之前的 SDK 版本。

  1. 位图存储在本机堆中,但它会自动收集垃圾,无需调用 recycle()。

  2. 如果 {VM heap size} + {allocated native heap memory} >= {VM heap size limit for the device},并且您正在尝试创建位图,则会抛出 OOM。

    注意:计算 VM HEAP SIZE 而不是 VM ALLOCATED MEMORY。

  3. 虚拟机堆大小在增长后永远不会缩小,即使分配的虚拟机内存缩小了。

  4. 因此,您必须将峰值 VM 内存保持在尽可能低的水平,以防止 VM 堆大小变得太大而无法为位图节省可用内存。

  5. 手动调用 System.gc() 是没有意义的,系统会在尝试增加堆大小之前先调用它。

  6. Native Heap Size 也永远不会缩小,但不计入 OOM,因此无需担心。

那么,我们来说说 SDK 从 Honey Comb 开始。

  1. 位图存储在 VM 堆中,不计入 OOM 的本机内存。

  2. OOM 的条件要简单得多:{VM heap size} >= {VM heap size limit for the device}。

  3. 所以你有更多的可用内存来创建具有相同堆大小限制的位图,OOM 不太可能被抛出。

这是我对垃圾收集和内存泄漏的一些观察。

您可以在应用程序中自己查看。如果 Activity 执行了在 Activity 销毁后仍在运行的 AsyncTask,则在 AsyncTask 完成之前,Activity 不会被垃圾收集。

这是因为 AsyncTask 是一个匿名内部类的实例,它持有 Activity 的引用。

如果任务在后台线程的 IO 操作中被阻塞,则调用 AsyncTask.cancel(true) 不会停止执行。

回调也是匿名的内部类,所以如果你的项目中的一个静态实例持有它们而不释放它们,内存就会泄漏。

如果您安排了重复或延迟的任务,例如 Timer,并且您没有在 onPause() 中调用 cancel() 和 purge(),则内存将会泄漏。

于 2012-12-01T03:50:37.753 回答
67

最近看到很多关于OOM异常和缓存的问题。开发人员指南对此有一篇非常好的文章,但有些人往往无法以合适的方式实现它。

因此,我编写了一个示例应用程序来演示 Android 环境中的缓存。此实现尚未获得 OOM。

查看此答案的末尾以获取指向源代码的链接。

要求:

  • Android API 2.1 或更高版本(我根本无法在 API 1.6 中为应用程序获取可用内存 - 这是唯一在 API 1.6 中不起作用的代码)
  • 安卓支持包

截屏

特征:

  • 如果方向发生变化,则保留缓存,使用单例
  • 将分配的应用程序内存的八分之一用于缓存(根据需要进行修改)
  • 大位图被缩放(您可以定义要允许的最大像素)
  • 在下载位图之前控制是否有可用的 Internet 连接
  • 确保每行只实例化一个任务
  • 如果您要扔掉ListView,它根本不会下载之间的位图

这不包括:

  • 磁盘缓存。无论如何,这应该很容易实现 - 只需指向一个从磁盘中获取位图的不同任务

示例代码:

正在下载的图像是来自 Flickr 的图像 (75x75)。但是,放置您想要处理的任何图像 url,如果超过最大值,应用程序将缩小它。在这个应用程序中,url 只是在一个String数组中。

有一个很好的LruCache方法来处理位图。然而,在这个应用程序中,我将一个实例LruCache放在我创建的另一个缓存类中,以使应用程序更可行。

Cache.java的关键东西(loadBitmap()方法最重要):

public Cache(int size, int maxWidth, int maxHeight) {
    // Into the constructor you add the maximum pixels
    // that you want to allow in order to not scale images.
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;

    mBitmapCache = new LruCache<String, Bitmap>(size) {
        protected int sizeOf(String key, Bitmap b) {
            // Assuming that one pixel contains four bytes.
            return b.getHeight() * b.getWidth() * 4;
        }
    };

    mCurrentTasks = new ArrayList<String>();    
}

/**
 * Gets a bitmap from cache. 
 * If it is not in cache, this method will:
 * 
 * 1: check if the bitmap url is currently being processed in the
 * BitmapLoaderTask and cancel if it is already in a task (a control to see
 * if it's inside the currentTasks list).
 * 
 * 2: check if an internet connection is available and continue if so.
 * 
 * 3: download the bitmap, scale the bitmap if necessary and put it into
 * the memory cache.
 * 
 * 4: Remove the bitmap url from the currentTasks list.
 * 
 * 5: Notify the ListAdapter.
 * 
 * @param mainActivity - Reference to activity object, in order to
 * call notifyDataSetChanged() on the ListAdapter.
 * @param imageKey - The bitmap url (will be the key).
 * @param imageView - The ImageView that should get an
 * available bitmap or a placeholder image.
 * @param isScrolling - If set to true, we skip executing more tasks since
 * the user probably has flinged away the view.
 */
public void loadBitmap(MainActivity mainActivity, 
        String imageKey, ImageView imageView,
        boolean isScrolling) {
    final Bitmap bitmap = getBitmapFromCache(imageKey); 

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);
        if (!isScrolling && !mCurrentTasks.contains(imageKey) && 
                mainActivity.internetIsAvailable()) {
            BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
                    mainActivity.getAdapter());
            task.execute();
        }
    } 
}

除非您想实现磁盘缓存,否则您不需要编辑 Cache.java 文件中的任何内容。

MainActivity.java 的关键内容:

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (view.getId() == android.R.id.list) {
        // Set scrolling to true only if the user has flinged the       
        // ListView away, hence we skip downloading a series
        // of unnecessary bitmaps that the user probably
        // just want to skip anyways. If we scroll slowly it
        // will still download bitmaps - that means
        // that the application won't wait for the user
        // to lift its finger off the screen in order to
        // download.
        if (scrollState == SCROLL_STATE_FLING) {
            mIsScrolling = true;
        } else {
            mIsScrolling = false;
            mListAdapter.notifyDataSetChanged();
        }
    } 
}

// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {           
    View row = convertView;
    final ViewHolder holder;

    if (row == null) {
        LayoutInflater inflater = getLayoutInflater();
        row = inflater.inflate(R.layout.main_listview_row, parent, false);  
        holder = new ViewHolder(row);
        row.setTag(holder);
    } else {
        holder = (ViewHolder) row.getTag();
    }   

    final Row rowObject = getItem(position);

    // Look at the loadBitmap() method description...
    holder.mTextView.setText(rowObject.mText);      
    mCache.loadBitmap(MainActivity.this,
            rowObject.mBitmapUrl, holder.mImageView,
            mIsScrolling);  

    return row;
}

getView()经常被调用。如果我们没有实施检查以确保我们不会在每行启动无限数量的线程,那么在此处下载图像通常不是一个好主意。Cache.java 检查是否rowObject.mBitmapUrl已经在一个任务中,如果是,它不会启动另一个。因此,我们很可能不会超出AsyncTask池中的工作队列限制。

下载:

您可以从https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip下载源代码。


最后的话:

我已经测试了几个星期了,我还没有得到一个 OOM 异常。我已经在模拟器、Nexus One 和 Nexus S 上对此进行了测试。我已经测试了包含高清质量图像的图像 URL。唯一的瓶颈是下载需要更多时间。

只有一种可能的情况我可以想象会出现OOM,那就是如果我们下载许多非常大的图像,并且在它们被缩放并放入缓存之前,会同时占用更多内存并导致OOM。但这无论如何都不是一个理想的情况,而且很可能无法以更可行的方式解决。

在评论中报告错误!:-)

于 2012-08-24T10:32:15.710 回答
45

我做了以下操作来拍摄图像并动态调整它的大小。希望这可以帮助

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
于 2009-02-16T06:23:31.773 回答
39

不幸的是,如果上述方法均无效,请将其添加到您的清单文件中。内部应用程序标签

 <application
         android:largeHeap="true"
于 2015-05-19T05:51:36.970 回答
36

似乎这是一个长期存在的问题,有很多不同的解释。我在这里接受了两个最常见的答案的建议,但是这些都没有解决我的虚拟机问题,声称它无法负担执行解码部分的字节。经过一番挖掘,我了解到这里真正的问题是解码过程从NATIVE堆中取出。

见这里:BitmapFactory OOM 让我抓狂

这导致我进入另一个讨论线程,在那里我找到了更多解决这个问题的方法。System.gc();一种是在显示图像后手动调用。但这实际上会使您的应用程序使用更多内存,以减少本机堆。自 2.0 (Donut) 版本起,更好的解决方案是使用 BitmapFactory 选项“inPurgeable”。所以我o2.inPurgeable=true;只是在o2.inSampleSize=scale;.

更多关于该主题的信息:内存堆的限制是否只有 6M?

现在,说了这么多,我对 Java 和 Android 也是个十足的笨蛋。因此,如果您认为这是解决此问题的糟糕方法,那么您可能是对的。;-) 但这对我来说很神奇,我发现现在不可能在堆缓存之外运行虚拟机。我能找到的唯一缺点是您正在破坏缓存的绘制图像。这意味着如果你回到那个图像,你每次都在重绘它。就我的应用程序的工作方式而言,这并不是一个真正的问题。你的旅费可能会改变。

于 2011-05-19T16:49:15.803 回答
34

使用它bitmap.recycle();这有助于没有任何图像质量问题。

于 2010-11-07T11:08:19.227 回答
31

我已经通过以下方式解决了同样的问题。

Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
    b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
    b.eraseColor(0xFFFFFFFF);
    Rect r = new Rect(0, 0,320 , 424);
    Canvas c = new Canvas(b);
    Paint p = new Paint();
    p.setColor(0xFFC0C0C0);
    c.drawRect(r, p);
    d = mContext.getResources().getDrawable(mImageIds[position]);
    d.setBounds(r);
    d.draw(c);

    /*   
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inTempStorage = new byte[128*1024];
        b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
        o2.inSampleSize=16;
        o2.inPurgeable = true;
    */
} catch (Exception e) {

}
i.setImageBitmap(b);
于 2011-06-04T09:44:58.510 回答
30

我有一个更有效的解决方案,不需要任何形式的缩放。只需将位图解码一次,然后根据其名称将其缓存在地图中。然后只需根据名称检索位图并将其设置在 ImageView 中。没有什么需要做的了。

这将起作用,因为解码位图的实际二进制数据未存储在 dalvik VM 堆中。它存储在外部。因此,每次解码位图时,它都会在 VM 堆之外分配内存,而 GC 永远不会回收这些内存

为了帮助您更好地理解这一点,假设您已将您的图像保存在可绘制文件夹中。您只需通过执行 getResources().getDrwable(R.drawable.) 来获取图像。这不会每次都解码您的图像,而是在您每次调用它时重新使用已解码的实例。所以本质上它是被缓存的。

现在,由于您的图像在某个文件中(或者甚至可能来自外部服务器),因此您有责任缓存解码的位图实例,以便在需要的任何地方重复使用。

希望这可以帮助。

于 2011-03-02T18:17:45.603 回答
29

这里有两个问题......

  • 位图内存不在 VM 堆中,而是在本机堆中 - 请参阅BitmapFactory OOM 让我发疯
  • 本机堆的垃圾收集比 VM 堆更懒惰 - 因此每次通过 Activity 的 onPause 或 onDestroy 时,您需要非常积极地执行 bitmap.recycle 和 bitmap =null
于 2011-05-12T04:40:43.797 回答
29

这对我有用!

public Bitmap readAssetsBitmap(String filename) throws IOException {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options(); 
        options.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
        if(bitmap == null) {
            throw new IOException("File cannot be opened: It's value is null");
        } else {
            return bitmap;
        }
    } catch (IOException e) {
        throw new IOException("File cannot be opened: " + e.getMessage());
    }
}
于 2012-06-09T20:33:32.800 回答
22

这里的答案很好,但我想要一个完全可用的类来解决这个问题。所以我做了一个。

这是我的BitmapHelper 类,它是 OutOfMemoryError 证明:-)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}
于 2013-02-03T15:03:02.763 回答
21

上面的答案都不适合我,但我确实想出了一个非常丑陋的解决方法来解决这个问题。我在我的项目中添加了一个非常小的 1x1 像素图像作为资源,并在调用垃圾收集之前将其加载到我的 ImageView 中。我认为可能是 ImageView 没有释放 Bitmap,所以 GC 从来没有把它捡起来。这很丑陋,但它现在似乎正在工作。

if (bitmap != null)
{
  bitmap.recycle();
  bitmap = null;
}
if (imageView != null)
{
  imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();

imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.
于 2011-12-07T12:32:34.477 回答
20

这对我有用。

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

这是在 C# monodroid 上。您可以轻松更改图像的路径。这里重要的是要设置的选项。

于 2012-10-02T10:33:28.687 回答
17

这似乎是与社区分享我的用于加载和处理图像的实用程序类的合适地方,欢迎您使用它并自由修改它。

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://stackoverflow.com/users/220710/emil
 *
 */
public class ImageProcessing {

    public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    public static Dimensions getDimensions(InputStream stream) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Dimensions getDimensions(String imgPath) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    private static boolean checkDecode(BitmapFactory.Options options){
        // Did decode work?
        if( options.outWidth<0 || options.outHeight<0 ){
            return false;
        }else{
            return true;
        }
    }

    /**
     * Creates a Bitmap that is of the minimum dimensions necessary
     * @param bm
     * @param min
     * @return
     */
    public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
        int newWidth, newHeight;
        switch(min.type){
        case WIDTH:
            if(bm.getWidth()>min.minWidth){
                newWidth=min.minWidth;
                newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case HEIGHT:
            if(bm.getHeight()>min.minHeight){
                newHeight=min.minHeight;
                newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case BOTH: // minimize to the maximum dimension
        case MAX:
            if(bm.getHeight()>bm.getWidth()){
                // Height needs to minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
                if(bm.getHeight()>min.minDim){
                    newHeight=min.minDim;
                    newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }else{
                // Width needs to be minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
                if(bm.getWidth()>min.minDim){
                    newWidth=min.minDim;
                    newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }
            break;
        default:
            // No resize
            newWidth=bm.getWidth();
            newHeight=bm.getHeight();
        }
        return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
    }

    public static int getScaledWidth(int height, Bitmap bm){
        return (int)(((double)bm.getWidth()/bm.getHeight())*height);
    }

    public static int getScaledHeight(int width, Bitmap bm){
        return (int)(((double)bm.getHeight()/bm.getWidth())*width);
    }

    /**
     * Get the proper sample size to meet minimization restraints
     * @param dim
     * @param min
     * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
     * @return
     */
    public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
        switch(min.type){
        case WIDTH:
            return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
        case HEIGHT:
            return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
        case BOTH:
            int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
            int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
            // Return the smaller of the two
            if(widthMaxSampleSize<heightMaxSampleSize){
                return widthMaxSampleSize;
            }else{
                return heightMaxSampleSize;
            }
        case MAX:
            // Find the larger dimension and go bases on that
            if(dim.width>dim.height){
                return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
            }else{
                return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
            }
        }
        return 1;
    }

    public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
        int add=multipleOf2 ? 2 : 1;
        int size=0;
        while(min<(dim/(size+add))){
            size+=add;
        }
        size = size==0 ? 1 : size;
        return size;        
    }

    public static class Dimensions {
        int width;
        int height;

        public Dimensions(int width, int height) {
            super();
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return width+" x "+height;
        }
    }

    public static class Minimize {
        public enum Type {
            WIDTH,HEIGHT,BOTH,MAX
        }
        Integer minWidth;
        Integer minHeight;
        Integer minDim;
        Type type;

        public Minimize(int min, Type type) {
            super();
            this.type = type;
            switch(type){
            case WIDTH:
                this.minWidth=min;
                break;
            case HEIGHT:
                this.minHeight=min;
                break;
            case BOTH:
                this.minWidth=min;
                this.minHeight=min;
                break;
            case MAX:
                this.minDim=min;
                break;
            }
        }

        public Minimize(int minWidth, int minHeight) {
            super();
            this.type=Type.BOTH;
            this.minWidth = minWidth;
            this.minHeight = minHeight;
        }

    }

    /**
     * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
     * @param width
     * @param height
     * @param config
     * @return
     */
    public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
        long pixels=width*height;
        switch(config){
        case ALPHA_8: // 1 byte per pixel
            return pixels;
        case ARGB_4444: // 2 bytes per pixel, but depreciated
            return pixels*2;
        case ARGB_8888: // 4 bytes per pixel
            return pixels*4;
        case RGB_565: // 2 bytes per pixel
            return pixels*2;
        default:
            return pixels;
        }
    }

    private static BitmapFactory.Options getOptionsForDimensions(){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        return options;
    }

    private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = sampleSize;
        options.inScaled = false;
        options.inPreferredConfig = bitmapConfig;
        return options;
    }
}
于 2012-11-02T23:41:44.470 回答
17

在我的一个应用程序中,我需要从Camera/Gallery. 如果用户单击相机中的图像(可能是 2MP、5MP 或 8MP),图像大小从kBs 到MBs 不等。如果图像大小小于(或高达 1-2MB)以上代码工作正常但如果我的图像大小超过 4MB 或 5MB 则OOM进入帧:(

然后我一直在努力解决这个问题,最后我对 Fedor 的代码进行了以下改进(感谢 Fedor 做出了如此好的解决方案)代码 :)

private Bitmap decodeFile(String fPath) {
    // Decode image size
    BitmapFactory.Options opts = new BitmapFactory.Options();
    /*
     * If set to true, the decoder will return null (no bitmap), but the
     * out... fields will still be set, allowing the caller to query the
     * bitmap without having to allocate the memory for its pixels.
     */
    opts.inJustDecodeBounds = true;
    opts.inDither = false; // Disable Dithering mode
    opts.inPurgeable = true; // Tell to gc that whether it needs free
                                // memory, the Bitmap can be cleared
    opts.inInputShareable = true; // Which kind of reference will be used to
                                    // recover the Bitmap data after being
                                    // clear, when it will be used in the
                                    // future

    BitmapFactory.decodeFile(fPath, opts);

    // The new size we want to scale to
    final int REQUIRED_SIZE = 70;

    // Find the correct scale value. 
    int scale = 1;

    if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) opts.outHeight
                / (float) REQUIRED_SIZE);
        final int widthRatio = Math.round((float) opts.outWidth
                / (float) REQUIRED_SIZE);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
    }

    // Decode bitmap with inSampleSize set
    opts.inJustDecodeBounds = false;

    opts.inSampleSize = scale;

    Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
            Bitmap.Config.RGB_565, false);

    return bm;

}

希望对遇到同样问题的小伙伴有所帮助!

有关更多信息,请参阅

于 2013-02-06T14:53:59.943 回答
15

几分钟前我刚刚遇到了这个问题。我通过更好地管理我的 listview 适配器解决了这个问题。我认为这是我使用的数百个 50x50px 图像的问题,结果我试图在每次显示该行时放大我的自定义视图。只需通过测试来查看该行是否被夸大,我就消除了这个错误,并且我使用了数百个位图。这实际上适用于 Spinner,但基本适配器对 ListView 的工作方式完全相同。这个简单的修复也大大提高了适配器的性能。

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    if(convertView == null){
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.spinner_row, null);
    }
...
于 2012-02-24T19:08:16.383 回答
15

此问题仅发生在 Android 模拟器中。我在模拟器中也遇到了这个问题,但是当我签入设备时它工作正常。

所以请检查设备。它可以在设备中运行。

于 2012-08-13T11:39:42.730 回答
14

我花了一整天的时间测试这些解决方案,唯一对我有用的是上述获取图像和手动调用 GC 的方法,我知道这不应该是必要的,但它是唯一有效的方法当我将我的应用程序置于活动之间切换的重负载测试中时。我的应用程序在列表视图中有一个缩略图列表(假设活动 A),当您单击其中一个图像时,它会将您带到另一个活动(假设活动 B),该活动显示该项目的主图像。当我在两个活动之间来回切换时,我最终会收到 OOM 错误,并且应用程序会强制关闭。

当我到达列表视图的一半时,它会崩溃。

现在,当我在活动 B 中实现以下内容时,我可以毫无问题地浏览整个列表视图并继续前进……而且速度非常快。

@Override
public void onDestroy()
{   
    Cleanup();
    super.onDestroy();
}

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
于 2012-03-27T19:33:09.843 回答
14

一般 android 设备堆大小只有 16MB(因​​设备/操作系统而异,请参阅帖子堆大小),如果您正在加载图像并且超过 16MB 的大小,它将抛出内存异常,而不是使用 Bitmap 进行加载来自 SD 卡或资源甚至网络的图像尝试使用getImageUri,加载位图需要更多内存,或者如果您使用该位图完成工作,您可以将位图设置为空。

于 2012-11-26T05:48:17.370 回答
14

这里的所有解决方案都需要设置 IMAGE_MAX_SIZE。这限制了具有更强大硬件的设备,如果图像尺寸太小,它在高清屏幕上看起来很难看。

我提出了一种适用于我的三星 Galaxy S3 和其他几款设备(包括功能较弱的设备)的解决方案,当使用功能更强大的设备时,图像质量会更好。

它的要点是计算在特定设备上为应用程序分配的最大内存,然后在不超过此内存的情况下将比例设置为尽可能低。这是代码:

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

我将此位图使用的最大内存设置为最大分配内存的 25%,您可能需要根据自己的需要进行调整,并确保此位图已清理干净,并且在您完成使用后不会留在内存中。通常,我使用此代码执行图像旋转(源位图和目标位图),因此我的应用程序需要同时在内存中加载 2 个位图,并且 25% 为我提供了一个很好的缓冲区,而不会在执行图像旋转时耗尽内存。

希望这可以帮助那里的人..

于 2013-08-31T03:04:47.633 回答
14

对从 SdCard 中选择的每个图像或可绘制的图像使用这些代码来转换位图对象。

Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    bitmap = Bitmap.createScaledBitmap(BitmapFactory
        .decodeFile(ImageData_Path.get(img_pos).getPath()),
        width, height, true);
} catch (OutOfMemoryError e) {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Config.RGB_565;
    options.inSampleSize = 1;
    options.inPurgeable = true;
    bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
        .getPath().toString(), options), width, height,true);
}
return bitmap;

使用ImageData_Path.get(img_pos).getPath()的图像路径。

于 2014-01-24T12:57:55.427 回答
13

这种OutofMemoryException不能通过调用System.gc()等来完全解决。

通过参考活动生命周期

活动状态由操作系统本身决定,取决于每个进程的内存使用情况和每个进程的优先级。

您可以考虑使用的每个位图图片的大小和分辨率。我建议缩小尺寸,重新采样到更低的分辨率,参考画廊的设计(一张小图片PNG,一张原始图片。)

于 2012-08-08T04:36:25.203 回答
13

此代码将有助于从 drawable 加载大位图

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
于 2012-10-31T12:03:07.970 回答
13

我的 2 美分:我通过位图解决了 OOM 错误:

a) 将我的图像缩放 2 倍

b)在我的自定义适配器中为 ListView 使用Picasso库,在 getView 中进行一次调用,如下所示:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

于 2013-07-14T09:08:14.007 回答
7
BitmapFactory.Options options = new Options();
options.inSampleSize = 32;
//img = BitmapFactory.decodeFile(imageids[position], options);

Bitmap theImage = BitmapFactory.decodeStream(imageStream,null, options);
Bitmap img=theImage.copy(Bitmap.Config.RGB_565,true);
theImage.recycle();
theImage = null;
System.gc();
//ivlogdp.setImageBitmap(img);
Runtime.getRuntime().gc();
于 2013-07-24T12:00:55.817 回答
7

您使用的图像似乎非常大。因此,由于堆内存已满,一些旧设备会崩溃。在旧设备(蜂蜜梳或 ICS 或任何低端型号设备)中,尝试android:largeHeap="true"在应用程序标签下的清单文件中使用或使用以下代码减小位图的大小。

Bitmap bMap;
BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InSampleSize = 8;
bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

你也可以给 4 或 12 或 16 来减小位图大小

于 2014-12-18T09:40:38.610 回答
5

我尝试了 Thomas Vervest 的方法,但是当 IMAGE_MAX_SIZE 为 2048 时,它返回的比例为 1,图像大小为 2592x1944。

根据其他人提供的所有其他评论,此版本对我有用:

private Bitmap decodeFile (File f) {
    Bitmap b = null;
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options ();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream (f);
        try {
            BitmapFactory.decodeStream (fis, null, o);
        } finally {
            fis.close ();
        }

        int scale = 1;
        for (int size = Math.max (o.outHeight, o.outWidth); 
            (size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options ();
        o2.inSampleSize = scale;
        fis = new FileInputStream (f);
        try {
            b = BitmapFactory.decodeStream (fis, null, o2);
        } finally {
            fis.close ();
        }
    } catch (IOException e) {
    }
    return b;
}
于 2012-12-09T21:45:45.753 回答
4

使用这个概念这将帮助你,然后在图像视图上设置图像位图

public static Bitmap convertBitmap(String path)   {

        Bitmap bitmap=null;
        BitmapFactory.Options bfOptions=new BitmapFactory.Options();
        bfOptions.inDither=false;                     //Disable Dithering mode
        bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
        bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
        bfOptions.inTempStorage=new byte[32 * 1024]; 


        File file=new File(path);
        FileInputStream fs=null;
        try {
            fs = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        try {
            if(fs!=null)
            {
                bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
            }
            } catch (IOException e) {

            e.printStackTrace();
        } finally{ 
            if(fs!=null) {
                try {
                    fs.close();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
        }

        return bitmap;
    }

如果您想从高度和宽度为 60 和 60 的大图像制作小图像并快速滚动列表视图,请使用此概念

public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth,
            int reqHeight) {

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        Bitmap bmp = BitmapFactory.decodeFile(path, options);
        return bmp;
        }

    public static int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {

        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            if (width > height) {
                inSampleSize = Math.round((float) height / (float) reqHeight);
            } else {
                inSampleSize = Math.round((float) width / (float) reqWidth);
             }
         }
         return inSampleSize;
        }

我希望它会对你有很大帮助。

你可以从开发者网站这里获得帮助

于 2013-08-29T06:34:00.200 回答
4

您好请访问链接http://developer.android.com/training/displaying-bitmaps/index.html

或者只是尝试使用给定函数检索位图

private Bitmap decodeBitmapFile (File f) {
    Bitmap bitmap = null;
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options ();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream (f);
        try {
            BitmapFactory.decodeStream (fis, null, o);
        } finally {
            fis.close ();
        }

        int scale = 1;
        for (int size = Math.max (o.outHeight, o.outWidth); 
            (size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);

        // Decode with input-stram SampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options ();
        o2.inSampleSize = scale;
        fis = new FileInputStream (f);
        try {
            bitmap  = BitmapFactory.decodeStream (fis, null, o2);
        } finally {
            fis.close ();
        }
    } catch (IOException e) {
    }
    return bitmap ;
}
于 2013-12-05T10:22:49.240 回答
4

要修复 OutOfMemory,您应该执行类似的操作,请尝试此代码

public Bitmap loadBitmap(String URL, BitmapFactory.Options options) {
                Bitmap bitmap = null;
                InputStream in = null;
                options.inSampleSize=4;
                try {
                    in = OpenHttpConnection(URL);
                    Log.e("In====>", in+"");
                    bitmap = BitmapFactory.decodeStream(in, null, options);
                    Log.e("URL====>", bitmap+"");

                    in.close();
                } catch (IOException e1) {
                }
                return bitmap;
            }

try {
                    BitmapFactory.Options bmOptions;
                    bmOptions = new BitmapFactory.Options();
                    bmOptions.inSampleSize = 1;
                    if(studentImage != null){
                        galleryThumbnail= loadBitmap(IMAGE_URL+studentImage, bmOptions);    
                    }

                    galleryThumbnail=getResizedBitmap(galleryThumbnail, imgEditStudentPhoto.getHeight(), imgEditStudentPhoto.getWidth());
                    Log.e("Image_Url==>",IMAGE_URL+studentImage+"");

                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
于 2013-12-21T11:10:09.677 回答
3

在浏览了所有的答案后,我惊讶地发现没有人提到处理图像的 Glide API。很棒的库,并抽象出位图管理的所有复杂性。您可以使用此库和一行代码快速加载和调整图像大小。

     Glide.with(this).load(yourImageResource).into(imageview);

您可以在此处获取存储库:https ://github.com/bumptech/glide

于 2017-12-15T18:38:16.660 回答
2

如果你像我一样懒惰,你可以开始使用Picasso 库来加载图像。

Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);
于 2017-07-12T14:38:43.570 回答
2

我使用了对我有用的解码文件描述符:

 FileInputStream  fileInputStream = null;
        try {
            fileInputStream  = new FileInputStream(file);
             FileDescriptor fd = fileInputStream.getFD();
            Bitmap imageBitmap = decodeSampledBitmapFromDescriptor(fd , 612,
                    816);
            imageView.setImageBitmap(imageBitmap);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

从文件描述符解码采样位图的代码:

 /**
     * Decode and sample down a bitmap from a file input stream to the requested width and height.
     *
     * @param fileDescriptor The file descriptor to read from
     * @param reqWidth       The requested width of the resulting bitmap
     * @param reqHeight      The requested height of the resulting bitmap
     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
     * that are equal to or greater than the requested width and height
     */
    public static Bitmap decodeSampledBitmapFromDescriptor(
            FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
    }

    /**
     * Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding
     * bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates
     * the closest inSampleSize that will result in the final decoded bitmap having a width and
     * height equal to or larger than the requested width and height. This implementation does not
     * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
     * results in a larger bitmap which isn't as useful for caching purposes.
     *
     * @param options   An options object with out* params already populated (run through a decode*
     *                  method with inJustDecodeBounds==true
     * @param reqWidth  The requested width of the resulting bitmap
     * @param reqHeight The requested height of the resulting bitmap
     * @return The value to be used for inSampleSize
     */
    public static int calculateInSampleSize(BitmapFactory.Options options,
                                            int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
            // with both dimensions larger than or equal to the requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;

            // This offers some additional logic in case the image has a strange
            // aspect ratio. For example, a panorama may have a much larger
            // width than height. In these cases the total pixels might still
            // end up being too large to fit comfortably in memory, so we should
            // be more aggressive with sample down the image (=larger inSampleSize).

            final float totalPixels = width * height;

            // Anything more than 2x the requested pixels we'll sample down further
            final float totalReqPixelsCap = reqWidth * reqHeight * 2;

            while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                inSampleSize++;
            }
        }
        return inSampleSize;
    }
于 2017-07-20T15:46:18.417 回答
2

这将获得适当的位图并减少内存消耗

JAVA

Bitmap bm = null;

BitmapFactory.Options bmpOption = new BitmapFactory.Options();
bmpOption.inJustDecodeBounds = true;

FileInputStream fis = new FileInputStream(file);
BitmapFactory.decodeStream(fis, null, bmpOption);
fis.close();

int scale = 1;

if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE) {
    scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
       (double) Math.max(bmpOption.outHeight, bmpOption.outWidth)) / Math.log(0.5)));
}

BitmapFactory.Options bmpOption2 = new BitmapFactory.Options();
bmpOption2.inSampleSize = scale;
fis = new FileInputStream(file);
bm = BitmapFactory.decodeStream(fis, null, bmpOption2);
fis.close();

科特林

val bm:Bitmap = null
val bmpOption = BitmapFactory.Options()
bmpOption.inJustDecodeBounds = true
val fis = FileInputStream(file)
BitmapFactory.decodeStream(fis, null, bmpOption)
fis.close()
val scale = 1
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE)
{
  scale = Math.pow(2.0, Math.ceil((Math.log((IMAGE_MAX_SIZE / Math.max(bmpOption.outHeight, bmpOption.outWidth) as Double)) / Math.log(0.5))).toInt().toDouble()).toInt()
}
val bmpOption2 = BitmapFactory.Options()
bmpOption2.inSampleSize = scale
fis = FileInputStream(file)
bm = BitmapFactory.decodeStream(fis, null, bmpOption2)
fis.close()
于 2019-03-08T13:45:00.983 回答
0

避免内存泄漏或位图 OOM 的最佳实践

  1. 不要为 Context / Activity 长期保存位图引用。
  2. 如果您在应用程序中使用大位图作为背景或其他内容,则不要将完整图像拉入主内存。您可以使用位图的 insample size 属性来获得屏幕所需的大小。
  3. 清洁位图参考一次不再使用。
于 2019-06-25T05:33:01.850 回答
0

我需要将一个大尺寸的图像加载到位图中,我使用 Glide 来解决这个问题。首先使用 inJustDecodeBounds 设置为 true 并使用 BitmapFactory.Options 检查图像大小,然后使用 Glide 获取 Bitmap 对象。我使用分析器检查内存使用情况,但没有像使用 BitmapFactory.decodeFile() 时那样看到任何内存峰值。我在使用 Xamarin 时使用 c# 编写,因此需要进行一些调整才能在 Java 中使用。 Glide 库文档

private Bitmap DecodeFile(File file) {
        // Decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        
        // setting inJustDecodeBounds to true won't load the file into memory, 
        // but gives you the actual file size.
        options.InJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(file), null, options);
        int actualWidth = options.OutWidth;
        int actualHeight = options.OutHeight;
                
        var ratio = (double)actualHeight / actualWidth;

        // Default to 800 x 600. changed the size whatever you need.
        var desiredWidth = 800;
        var desiredHeight = 600;

        if(actualHeight > actualWidth)
        {
            var ratio = (double)actualWidth / actualHeight;
            var futureTarget = Glide.With(Application.Context)
                .AsBitmap()
                .Load(file)
                .SetDiskCacheStrategy(DiskCacheStrategy.None)
                .SkipMemoryCache(true)
                .Submit((int)(desiredWidth * ratio), desiredWidth);
            bitmap = futureTarget.Get() as Bitmap;
        }
        else
        {
            var ratio = (double)actualHeight / actualWidth;
            var futureTarget = Glide.With(Application.Context)
                .AsBitmap()
                .Load(file)
                .SetDiskCacheStrategy(DiskCacheStrategy.None)
                .SkipMemoryCache(true)
                .Submit(desiredWidth, (int)(desiredWidth * ratio));
            bitmap = futureTarget.Get() as Bitmap;
        }return bitmap;}
于 2020-07-28T23:43:59.357 回答
-1

将以下行添加到您的 manifest.xml 文件中:

<application

    android:hardwareAccelerated="false"
    android:largeHeap="true">

    <activity>
    </activity>

</application>
于 2018-11-01T06:02:33.417 回答
-10

将位图设置为 imageview 后,像这样回收它:

bitmap.recycle();
bitmap=null;
于 2013-03-29T06:56:44.627 回答