【问题标题】:BitmapFactory.decodeStream out of memory despite using reduced sample size尽管使用了减小的样本大小,但 BitmapFactory.decodeStream 内存不足
【发布时间】:2013-02-21 15:39:55
【问题描述】:

我看了很多关于解码位图的内存分配问题的相关帖子,但是即使使用了官网提供的代码,我仍然无法找到以下问题的解决方案。

这是我的代码:

public static Bitmap decodeSampledBitmapFromResource(InputStream inputStream, int reqWidth, int reqHeight) {

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len;
    try {
        while ((len = inputStream.read(buffer)) > -1) {
        baos.write(buffer, 0, len);
        }
        baos.flush();
        InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
        InputStream is2 = new ByteArrayInputStream(baos.toByteArray());

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is1, null, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inPurgeable = true;
        options.inInputShareable = true;
        options.inJustDecodeBounds = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        return BitmapFactory.decodeStream(is2, null, options);

    } catch (Exception e) {
        e.printStackTrace();

        return null;
    }
}

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


    return inSampleSize;
}

bitmap = decodeSampledBitmapFromResource(inputStream, 600, 600); 

我在这一行收到“3250016 字节分配内存不足错误”:

return BitmapFactory.decodeStream(is2, null, options);

在我看来,3.2 MB 小到可以分配。我哪里错了?我该如何解决这个问题?

编辑

在通过 N-Joy 查看此解决方案 HERE 后,它适用于所需尺寸 300,但我所需尺寸是 800,所以我仍然收到错误。

【问题讨论】:

标签: android bitmap out-of-memory


【解决方案1】:

看看这个视频。 http://www.youtube.com/watch?v=_CruQY55HOk。不要按照视频中的建议使用 system.gc()。使用 MAT Analyzer 找出内存泄漏。我猜返回的位图太大导致内存泄漏。

【讨论】:

  • 即使我删除它也不能解决问题,有什么方法可以告诉我长时间卡住了
  • 删除不会解决问题。要解决您的问题,请使用 mat 分析器来找出需要多少内存位图。我猜它占用太多空间会导致内存泄漏。
  • 是的,它的内存很大,但是如何解决这个问题?这是个大问题
  • 压缩应该会减少一些内存使用。但是,如果它仍然很大导致内存泄漏,我恐怕无能为力。尝试压缩位图。
  • 怎么压缩?它会降低质量吗?
【解决方案2】:

您可能会保留以前的位图引用。我猜您多次执行此代码,但从未执行过bitmap.recycle()。内存将不可避免地耗尽。

【讨论】:

  • 当您完成位图时,您应该回收它。我无法回答这个问题,因为我不知道您在使用什么或如何使用它。
  • 是的,我尝试了您的解决方案,但仍然没有运气 :( 还有什么我遗漏的吗?
【解决方案3】:

看来你有大图要显示。

您可以下载图像并保存到 sdcard (example) 然后您可以使用this 显示来自 sdcard 的图像的代码。

【讨论】:

  • 假设calculateInSampleSize()返回8,这将精确地使用与他当前实现相同的内存量。
  • 不,我不能这样做,因为我有大约 200 张图片
  • 但您只需第一次下载。然后您可以显示来自 sdcard 的图像,这也将帮助您以更快的方式运行您的应用程序。
  • @MAC 不存在一些问题,例如如果用户从 sdcard 中删除文件怎么办?也不会占用用户sdcard的空间,
  • 但是如果你要使用这种方式,每次迭代都会浪费带宽和时间。
【解决方案4】:

这是用户在玩大位图时通常会遇到的常见问题,并且现场讨论了很多问题,herehereherehere 等等,即使用户没有能够操纵精确的解决方案。

我偶然发现了一个库,它可以流畅地管理位图以及我在下面列出的其他链接。希望这可以帮助!

smoothie-Library

Android-BitmapCache

Android-Universal-Image-Loader Solution for OutOfMemoryError: bitmap size exceeds VM budget

【讨论】:

    【解决方案5】:

    我在位图内存使用方面遇到了很多问题。

    结果:

    • 大多数设备的图形堆内存有限,大多数小型设备的整个应用程序都限制为 16MB,而不仅仅是您的应用程序
    • 如果适用,使用 4 位或 8 位或 16 位位图
    • 尝试从头开始绘制形状,尽可能省略位图。

    【讨论】:

      【解决方案6】:

      使用 WebView 动态加载任意数量的图像,它是使用 NDK(低级)构建的,因此没有 GDI 堆内存限制。 它运行流畅快速:)

      【讨论】:

      • 它是滚动图像布局的绝佳替代品,例如 ViewFlipper
      【解决方案7】:

      decodeSampledBitmapFromResource 方法内存效率不高,因为它使用 3 个流:ByteArrayOutputStream baos、ByteArrayInputStream is1 和 ByteArrayInputStream is2,每个存储图像的相同流数据(每个一个字节数组)。

      当我使用我的设备 (LG nexus 4) 进行测试以将 SD 卡上的 2560x1600 图像解码为目标尺寸 800 时,需要这样:

      03-13 15:47:52.557: E/DecodeBitmap(11177): dalvikPss (beginning) = 1780
      03-13 15:47:53.157: E/DecodeBitmap(11177): dalvikPss (decoding) = 26393
      03-13 15:47:53.548: E/DecodeBitmap(11177): dalvikPss (after all) = 30401 time = 999
      

      我们可以看到:分配了太多的内存 (28.5 MB) 只是为了解码 4096000 像素图像。

      解决方案:我们读取 InputStream 并将数据直接存储到一个字节数组中,并使用这个字节数组进行其余工作。
      示例代码:

      public Bitmap decodeSampledBitmapFromResourceMemOpt(
                  InputStream inputStream, int reqWidth, int reqHeight) {
      
              byte[] byteArr = new byte[0];
              byte[] buffer = new byte[1024];
              int len;
              int count = 0;
      
              try {
                  while ((len = inputStream.read(buffer)) > -1) {
                      if (len != 0) {
                          if (count + len > byteArr.length) {
                              byte[] newbuf = new byte[(count + len) * 2];
                              System.arraycopy(byteArr, 0, newbuf, 0, count);
                              byteArr = newbuf;
                          }
      
                          System.arraycopy(buffer, 0, byteArr, count, len);
                          count += len;
                      }
                  }
      
                  final BitmapFactory.Options options = new BitmapFactory.Options();
                  options.inJustDecodeBounds = true;
                  BitmapFactory.decodeByteArray(byteArr, 0, count, options);
      
                  options.inSampleSize = calculateInSampleSize(options, reqWidth,
                          reqHeight);
                  options.inPurgeable = true;
                  options.inInputShareable = true;
                  options.inJustDecodeBounds = false;
                  options.inPreferredConfig = Bitmap.Config.ARGB_8888;
      
                  int[] pids = { android.os.Process.myPid() };
                  MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
                  Log.e(TAG, "dalvikPss (decoding) = " + myMemInfo.dalvikPss);
      
                  return BitmapFactory.decodeByteArray(byteArr, 0, count, options);
      
              } catch (Exception e) {
                  e.printStackTrace();
      
                  return null;
              }
          }
      

      进行计算的方法:

      public void onButtonClicked(View v) {
              int[] pids = { android.os.Process.myPid() };
              MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
              Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);
      
              long startTime = System.currentTimeMillis();
      
              FileInputStream inputStream;
              String filePath = Environment.getExternalStorageDirectory()
                      .getAbsolutePath() + "/test2.png";
              File file = new File(filePath);
              try {
                  inputStream = new FileInputStream(file);
      //          mBitmap = decodeSampledBitmapFromResource(inputStream, 800, 800);
                  mBitmap = decodeSampledBitmapFromResourceMemOpt(inputStream, 800,
                          800);
                  ImageView imageView = (ImageView) findViewById(R.id.image);
                  imageView.setImageBitmap(mBitmap);
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              }
              myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
              Log.e(TAG, "dalvikPss (after all) = " + myMemInfo.dalvikPss
                      + " time = " + (System.currentTimeMillis() - startTime));
          }
      

      结果:

      03-13 16:02:20.373: E/DecodeBitmap(13663): dalvikPss (beginning) = 1823
      03-13 16:02:20.923: E/DecodeBitmap(13663): dalvikPss (decoding) = 18414
      03-13 16:02:21.294: E/DecodeBitmap(13663): dalvikPss (after all) = 18414 time = 917
      

      【讨论】:

      • 使用这个我只得到一个灰色/黑色位图而不是我正在解码的图片,知道为什么吗?
      • 很棒的解决方案,正是我想要的。我唯一需要改变的是我没有将文件存储在外部 SD 卡上,因此只需将它们从我的可绘制文件夹移动到原始文件夹。
      • 什么是mAM?什么类型的变量? @Binh Tran
      • @Crishnan mAM 是 ActivityManager 的一个实例。
      • 知道了,谢谢@Binh Tran
      【解决方案8】:

      解码位图时出现的内存不足问题通常不会与您正在解码的图像大小相关联。 当然,如果你尝试打开一个 5000x5000px 的图像,你会因为 OutOfMemoryError 而失败,但是对于 800x800px 的大小,它是完全合理的,应该可以正常工作。

      如果您的设备因 3.2 MB 图像而出现内存不足,这可能是因为您在应用程序的某处泄漏了上下文。

      这是this post的第一部分:

      我猜问题不在你的布局中,问题在其他地方 你的代码。并且可能您在某处泄露了上下文

      这意味着您在不应该使用的组件中使用活动上下文,从而防止它们被垃圾收集。因为组件经常被活动持有,这些活动不是 GC,你的 Java 堆会增长得非常快,你的应用程序有时会崩溃。

      正如 Raghunandan 所说,您将不得不使用 MAT 来查找持有的 Activity/Component 并消除上下文泄漏。

      目前我发现检测上下文泄漏的最佳方法是改变方向。 例如,多次旋转 ActivityMain,运行 MAT 并检查是否只有一个 ActivityMain 实例。如果您有多个(与旋转变化一样多),则意味着存在上下文泄漏。

      几年前我发现了一个good tutorial on using MAT。也许现在有更好的。

      关于内存泄漏的其他帖子:

      Android - memory leak or?

      Out of memory error on android emulator, but not on device

      【讨论】:

        【解决方案9】:

        ARGB_8888 使用更多内存,因为它采用 Alpha 颜色值,所以我的建议是使用 RGB_565,如 HERE 所述

        注意:与ARGB_8888 相比,质量会稍低。

        【讨论】:

          【解决方案10】:

          我之前也遇到过同样的问题.. 我已经通过使用这个函数来管理它,你可以根据需要的宽度和高度来缩放。

          private Bitmap decodeFile(FileInputStream f)
          {
              try
              {
                  //decode image size
                  BitmapFactory.Options o = new BitmapFactory.Options();
                  o.inJustDecodeBounds = true;
                  BitmapFactory.decodeStream(f,null,o);
          
                  //Find the correct scale value. It should be the power of 2.
                  final int REQUIRED_SIZE=70;
                  int width_tmp=o.outWidth, height_tmp=o.outHeight;
                  int scale=1;
                  while(true)
                  {
                      if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
                          break;
                      width_tmp/=2;
                      height_tmp/=2;
                      scale*=2;
                  }
          
                  //decode with inSampleSize
                  BitmapFactory.Options o2 = new BitmapFactory.Options();
                  o2.inSampleSize=scale;
                  return BitmapFactory.decodeStream(f, null, o2);
              } 
              catch (FileNotFoundException e) {}
              return null;
          }
          

          并参考Memory Leak Error Androiderror in loading images to gridview android

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-05-18
            • 2015-02-12
            • 1970-01-01
            • 1970-01-01
            • 2019-02-04
            • 2021-07-28
            • 1970-01-01
            相关资源
            最近更新 更多