【问题标题】:Bitmap decodeStream OutOfMemory Exception位图 decodeStream OutOfMemory 异常
【发布时间】:2012-01-27 06:54:06
【问题描述】:

我在我的应用程序中使用我自己的 Android 实现 ViewFlow 示例。我正在从网络服务下载加密图像,然后将它们保存在 SD 卡上。我正在使用 viewflow 动态解密图像并显示它们。但问题是,当用户开始太快更改图像时,它会给我一个OutOfMemoryException,而我发现/测试的所有信息都不适用于我的情况。这是我正在使用的:

 @Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.image_item, null);
    }

    try {
        File bufferFile = new File(ids.get(position));
        FileInputStream fis   = new FileInputStream(bufferFile);

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec("01234567890abcde".getBytes(), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec("fedcba9876543210".getBytes());
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        CipherInputStream cis = new CipherInputStream(fis, cipher);

        BitmapFactory.Options o = new BitmapFactory.Options();
        final int REQUIRED_SIZE=300*1024;

        //Find the correct scale value. It should be the power of 2.
        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;

        Bitmap ops = BitmapFactory.decodeStream(cis,null,o2);
        ((ImageView) convertView.findViewById(R.id.imgView)).setImageBitmap(ops);
        cis.close();
        fis.close();

        System.gc();

    } catch (Exception e) {
        e.printStackTrace();
        ((ImageView) convertView.findViewById(R.id.imgView)).setImageResource(R.drawable.image_unavailablee);
    }

    return convertView;
}

而且它仍然在线抛出异常:

((ImageView) convertView.findViewById(R.id.imgView)).setImageBitmap(ops);

这个例外:

java.lang.OutOfMemoryError: bitmap size exceeds VM budget(Heap Size=6791KB, Allocated=3861KB, Bitmap Size=26006KB)

有什么办法解决这个问题吗?

【问题讨论】:

    标签: android bitmap out-of-memory


    【解决方案1】:

    仅供处理大型位图的任何人参考,有一篇文章展示了如何处理此类问题以避免内存不足的最佳方法!

    http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

    希望对你有帮助!

    【讨论】:

    • 这是我见过的最好、最干净、最重要的是最快的方法。非常感谢你的分享。有时最好的信息是 Android 小组创建或发布的信息,但这是我最后看到的地方。
    【解决方案2】:

    REQUIRED_SIZE 应该包含最大尺寸(宽度、高度,以像素为单位),例如

      final int REQUIRED_SIZE = 1024; // 1024 pixels wide or long.
    

    在计算缩放因子之前,您还遗漏了几行用于将图像边界设置为 BitmapFactory.Options o 的行。

        //Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(cis, null, o);
    
        //The new size we want to scale to
        final int REQUIRED_SIZE = 1024;
    

    然后使用o.outWidtho.outHeight 计算比例因子。您可能需要再次获取 cis 才能对流进行实际解码。

    更新:

    另外,您可以将以下变量作为适配器的成员并在构造函数中进行初始化。

    SecretKeySpec keySpec = new SecretKeySpec("01234567890abcde".getBytes(), "AES");
    IvParameterSpec ivSpec = new IvParameterSpec("fedcba9876543210".getBytes());
    

    这应该没有问题。

    【讨论】:

      【解决方案3】:

      取自这里: http://www.memofy.com/memofy/show/1008ab7f2836ab7f01071c2dbfe138/outofmemory-exception-when-decoding-with-bitmapfactory

      试试这个:

      BitmapFactory.Options o = new BitmapFactory.Options();
      options.inTempStorage = new byte[16*1024];
      
      Bitmap bitmapImage = BitmapFactory.decodeFile(path, options);
      

      代替:

      BitmapFactory.Options o = new BitmapFactory.Options();
          final int REQUIRED_SIZE=300*1024;
      

      因此,在使用 BitmapFactory.decodeFile() 之前创建一个 16kb 的字节数组,并将其传递到解码过程中的临时存储。

      希望对您有所帮助! 参考:Strange out of memory issue while loading an image to a Bitmap object

      【讨论】:

        【解决方案4】:

        我一遍又一遍地遇到同样的问题。

        这是我的代码,可能有点矫枉过正,但我​​不得不因为不同的相机尺寸、分辨率等原因,但你必须根据自己的需要调整它。

                    BitmapFactory.Options imageOptions = new BitmapFactory.Options();
                imageOptions.inJustDecodeBounds = true;
                ByteArrayInputStream imageByteArrayInputStream = new ByteArrayInputStream(data);
                BitmapFactory.decodeStream(imageByteArrayInputStream, null, imageOptions);
                System.gc();
        
                // Decode frame size
                BitmapFactory.Options frameOptions = new BitmapFactory.Options();
                frameOptions.inJustDecodeBounds = true;
                BitmapFactory.decodeResource(getResources(), selectedFrameResourceID, frameOptions);
                System.gc();
        
                // Scale factor for pre scaling
                int preScaleFactor = 1;
                if (imageOptions.outWidth > frameOptions.outWidth || imageOptions.outHeight > frameOptions.outHeight) {
                    preScaleFactor = Math.max(imageOptions.outWidth / frameOptions.outWidth, imageOptions.outHeight / frameOptions.outHeight);
                }
        
                // Decode with inSampleSize
                BitmapFactory.Options scaleOptions = new BitmapFactory.Options();
                scaleOptions.inSampleSize = preScaleFactor;
        
                imageByteArrayInputStream = new ByteArrayInputStream(data);
                Bitmap preScaledBitmap = BitmapFactory.decodeStream(imageByteArrayInputStream, null, scaleOptions);
                System.gc();
        
                Bitmap finalBitmap;
        
                // Scale factor for precise scaling
                // If the scaled image is not exactly the same size as the frame than resize it precisely
                if (preScaledBitmap.getWidth() != frameOptions.outWidth || preScaledBitmap.getHeight() != frameOptions.outHeight) {
                    float scaleFactor = Math.max((float)((float)preScaledBitmap.getWidth() / (float)frameOptions.outWidth), (float)((float)preScaledBitmap.getHeight() / (float)frameOptions.outHeight));
                    float scalePercentage = Math.min((float)((float)frameOptions.outWidth / (float)preScaledBitmap.getWidth()), (float)((float)frameOptions.outHeight / (float)preScaledBitmap.getHeight()));
        
                    Matrix matrix = new Matrix();
                    matrix.preScale(scalePercentage, scalePercentage);
        
                    // If the capture width for the source is bigger than the actual width of the source, then set is to the max of the actual source width
                    int sourceCaptureWidth = (int)(frameOptions.outWidth * scaleFactor);
                    if (sourceCaptureWidth > preScaledBitmap.getWidth()) {
                        sourceCaptureWidth = preScaledBitmap.getWidth();
                    }
        
                    // Same as above but than for the height
                    int sourceCaptureHeight = (int)(frameOptions.outHeight * scaleFactor);
                    if (sourceCaptureHeight > preScaledBitmap.getHeight()) {
                        sourceCaptureHeight = preScaledBitmap.getHeight();
                    }
        
                    finalBitmap = Bitmap.createBitmap(preScaledBitmap, 0, 0, sourceCaptureWidth, sourceCaptureHeight, matrix, true);
        
                    preScaledBitmap.recycle();
                    preScaledBitmap = null;
        

        希望对你有帮助

        【讨论】:

        • 实际上我在我的 getView 函数中实现了延迟加载,它现在可以正常工作,没有任何错误。我想整个问题在于我在 getView 函数中解码流,它分配了太多内存。无论如何感谢您的回答!如果它仍然给我一个错误,我会尝试你的解决方案。
        • 没问题,我很高兴你明白了。
        • 先生,我想向您致敬。几年前我发明了自己的例程来做同样的事情,但我更喜欢你的。非常令人印象深刻。虽然我最终从 Leonardo Arango Baena 的回答中窃取了 Android 提供的“提示”,这几乎像是作弊!
        【解决方案5】:

        使用回收()。它将释放与此位图关联的本机对象,并清除对像素数据的引用。

         Bitmap ops = BitmapFactory.decodeStream(cis,null,o2);
                ((ImageView) convertView.findViewById(R.id.imgView)).setImageBitmap(ops);
                cis.close();
                fis.close();
                ops.recycle();
                System.gc();
        

        【讨论】:

        • 当我尝试使用回收时,它抛出了这个异常:java.lang.RuntimeException:Canvas:尝试使用回收的位图 android.graphics.Bitmap@40627090, 640x960
        • 你可以在 finally 块中使用 recycle() 尝试这样做。
        【解决方案6】:

        您在哪个版本的 Android 上运行此程序?如果你在 Honeycomb 或更高版本上运行它,你应该能够使用 Eclipse 内存分析器来查看你的内存在哪里被使用。

        话虽如此,您需要在位图不再需要或显示时立即在位图上调用 recycle()(这是 Sujits 回答的问题)。换句话说,如果位图离开屏幕,最好回收它,然后在它重新出现时重新加载它。否则,该位图是 usi

        为此,请在 ImageView 上调用 getDrawable(),在 ImageView 上调用 setImageDrawable(null),然后将 drawable 转换为 BitmapDrawable 并回收其中的位图。

        有关位图内存在 Android 3.0 之前如何工作的更多信息,您可以查看我在此问题上所做的一篇文章:http://code.google.com/p/android/issues/detail?id=8488#c80

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-09-05
          • 1970-01-01
          • 1970-01-01
          • 2015-07-28
          • 2018-06-18
          • 2015-04-25
          相关资源
          最近更新 更多