如何在Android中加载大图像并避免内存不足错误?

时间:2014-01-27 22:24:21

标签: android bitmap out-of-memory

我正在开发一款使用大图片的应用程序(1390×870:150kb - 50kb)。当我点击触发器/ ImageView时,我正在添加图像。

在某个时刻我发现内存不足错误:

java.lang.OutOfMemoryError
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:613)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:378)

要调整图像大小,我正在执行此操作:

Bitmap productIndex = null;
final String imageLoc = IMAGE_LOCATION;
InputStream imageStream;
try {
     imageStream = new FileInputStream(imageLoc);
     productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);

     productIV.setImageBitmap(productIndex);
     } catch (FileNotFoundException e1) {
          // TODO Auto-generated catch block
          e1.printStackTrace();
     }
}


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

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

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

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

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 / 3;
    final int halfWidth = width / 3;

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

我采用这种方式调整大小以节省Android文档的空间: Loading Large Bitmaps Efficiently

根据日志,这就是decodeSampledBitmapFromResource方法中的罪魁祸首:

return BitmapFactory.decodeFile(resId, options);

-----编辑----- 以下是我将每个项目添加到FrameLayout的方法。

for(int ps=0;ps<productSplit.size();ps++){
    //split each product by the equals sign
    List<String> productItem = Arrays.asList(productSplit.get(ps).split("="));

    String tempCarID = productItem.get(0);
    tempCarID = tempCarID.replace(" ", "");
    if(String.valueOf(carID).equals(tempCarID)){

        ImageView productIV = new ImageView(Configurator.this);
        LayoutParams productParams = new LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        productIV.setId(Integer.parseInt(partIdsList.get(x)));
        productIV.setLayoutParams(productParams);

        final String imageLoc = productItem.get(2);

        InputStream imageStream;
        try {
            imageStream = new FileInputStream(imageLoc);
            productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
            productIV.setImageBitmap(productIndex);
        } catch (FileNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        productLayers.addView(productIV);

    }
}

6 个答案:

答案 0 :(得分:28)

您可以使用另一个bitmap-config来大幅减小图像的大小。默认为RGB-config ARGB8888,这意味着使用了四个8位通道(红色,绿色,蓝色,alhpa)。 Alpha是位图的透明度。这占用了大量的内存 - 图像化X 4.因此,如果图像大小为400万像素16兆字节将立即分配到堆上 - 快速耗尽内存。

相反 - 使用RGB_565在某种程度上会降低质量 - 但为了弥补这一点,你可以抖动图像。

所以 - 你的方法decodeSampledBitmapFromResource - 添加以下代码片段:

 options.inPreferredConfig = Config.RGB_565;
 options.inDither = true;

在您的代码中:

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

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

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

 // Decode bitmap with inSampleSize set
 options.inJustDecodeBounds = false;
 options.inPreferredConfig = Config.RGB_565;
 options.inDither = true;
 return BitmapFactory.decodeFile(resId, options);
 }

参考文献:

http://developer.android.com/reference/android/graphics/Bitmap.Config.html#ARGB_8888

答案 1 :(得分:25)

如果您的图像不在drawable-xxhdpi的正确文件夹中,则S4等高分辨率设备通常会耗尽内存。您也可以将图片放入drawable-nodpi。如果您的图像只是在drawable中,那么它将会耗尽memorey的原因,即android会缩放图像,认为图像是为低分辨率而设计的。

答案 2 :(得分:4)

您可以使用这个漂亮的图书馆https://github.com/davemorrissey/subsampling-scale-image-view

答案 3 :(得分:2)

Here is how I'm adding each item to the FrameLayout 这就是问题,代码不断添加和添加更多图像,并且无论你调整大小或设备有多少内存,在某些时候它会耗尽内存。那是因为你添加的每张图像都保存在内存中。

对于这种情况,应用程序所做的是使用可以回收视图的ViewGroup。我不知道您的布局,但通常是ListViewGridViewViewPager,通过回收您重新使用布局的视图,并可以根据需要处置重新加载图像。

出于加载和调整图像大小的特定目的,我强烈建议使用Picasso库,因为它编写得非常好,使用简单且稳定。

答案 4 :(得分:0)

您仍然需要管理位图内存,因为我不会尝试将总空间分配超过屏幕大小的3倍(如果您认为它对滚动行为有意义)。如果您将一个图像叠加在另一个图像上,则在某个时刻,您将遇到Out Of Memory错误。您可能需要将先前的屏幕图像捕获为单个背景图像,以确保您仍然适合可用内存。或者,当新图像与现有图像重叠时,仅加载并渲染可见部分。如果性能成为问题,那么您可能需要考虑OpenGL纹理,但内存分配问题仍然是相同的。

请仔细检查所有Displaying Bitmaps Training,因为它应该为您提供有关如何处理显示的其他想法。

答案 5 :(得分:0)

使用Fresco库加载大图像将避免此错误。 在xml布局中

<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/my_image_view"
    android:layout_width="1300dp"
    android:layout_height="1300dp"
    fresco:placeholderImage="@drawable/my_drawable"
  />

和javacode

Uri uri = Uri.parse("https://image.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);