【问题标题】:large bitmap crushes (out of memory)大位图压碎(内存不足)
【发布时间】:2023-03-15 03:57:01
【问题描述】:

我正在关注 developer.android 网站 here,我正在尝试从 url 加载大图像(即 1024 x 900)我的代码在我的 Google Nexus 中运行良好,但在 Nexus s 上无法运行。

这是我的代码

public static Bitmap getBitmapFromURL(String src) {
        Bitmap bmImg;
        URL myFileUrl = null;

        try {
            myFileUrl = new URL(src);

            HttpURLConnection conn= (HttpURLConnection)myFileUrl.openConnection();
            conn.setDoInput(true);
            conn.connect();
            InputStream is = conn.getInputStream();


            final BitmapFactory.Options options = new BitmapFactory.Options();

            BufferedInputStream bis = new BufferedInputStream(is, 4*1024);
            ByteArrayBuffer baf = new ByteArrayBuffer(50);
            int current = 0;
            while ((current = bis.read()) != -1) {
                baf.append((byte)current);
            }
            byte[] imageData = baf.toByteArray();

            BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options);
            options.inJustDecodeBounds = true;
            options.inSampleSize = calculateInSampleSize(options, 1024, 128);
            options.inJustDecodeBounds = false;
            Bitmap bitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length,options);
            return bitmap;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            return null;
        }
    } 

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        // 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;
    }

AsyncTask 类

class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
        private final WeakReference<ImageView> imageViewReference;
        private String data = null;

        public BitmapWorkerTask(ImageView imageView) {
            // Use a WeakReference to ensure the ImageView can be garbage collected
            imageViewReference = new WeakReference<ImageView>(imageView);
        }

        // Decode image in background.
        @Override
        protected Bitmap doInBackground(String... params) {
            data = params[0];
            return Util.getBitmapFromURL(data);
        }

        // Once complete, see if ImageView is still around and set bitmap.
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                if (imageView != null) {
                    imageView.setImageBitmap(Bitmap.createBitmap(bitmap, 0, bitmap.getHeight()/2, bitmap.getWidth(), bitmap.getHeight()/2));
                }
            }
        }
    }

当我在 Nexus 上运行时,出现内存不足异常。在关注 android 开发者网站时,我不明白我在这里做错了什么以及为什么我的应用程序在旧设备中崩溃。 请帮忙。

注意:我的图像宽度应始终为 match_parent。这就是为什么我把 calculateInSampleSize(option,1024,128)

【问题讨论】:

标签: android bitmap android-imageview bitmapimage android-bitmap


【解决方案1】:

您正在浪费内存,因为您在对整个图像进行一次解码后设置了 inJustDecodeBounds。 (我猜你以后会因为这个而得到OOM)。考虑关闭您的输入流。

  BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options);
  options.inJustDecodeBounds = true;
  options.inSampleSize = calculateInSampleSize(options, 1024, 128);
  options.inJustDecodeBounds = false;
  Bitmap bitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length,options);
  return bitmap;

我认为你是真的打算这样做:

  options.inJustDecodeBounds = true;
  BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options);
  options.inSampleSize = calculateInSampleSize(options, 1024, 128);
  options.inJustDecodeBounds = false;
  Bitmap bitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length,options);
  return bitmap;

这将在不存储位图的情况下获得图像的宽度和高度(只需获取选项),然后对图像进行二次采样以正确的大小并返回位图。

我不明白你为什么在你的异步任务的 onPostExexute 中再次调整位图大小, imageView.setImageBitmap(Bitmap.createBitmap(bitmap, 0, bitmap.getHeight()/2, bitmap.getWidth(), bitmap.getHeight()/2));

当您刚刚完成加载位图时,为什么不加载正确的尺寸?(在完成上述更改后,我建议您记录尺寸,看看是否真的需要在 onPostExexute 中再次调整大小)

你看过图片加载库吗?

有很多可用的,似乎适合您需要的一个是Picasso

在哪里可以做这样的事情: Picasso.with(context).load("url_to_image").fit().into(imageView);

这会下载和缓存位图(在后台线程中),并适合并绘制到 imageView 中,全部在一行中,整洁!

【讨论】:

  • 感谢@Magnus 的回答。你在这里做了很好的解释。这正是我在这里做错的 options.inJustDecodeBounds = true;必须在 decodeByteArray 之前设置....
  • 还有一件事我现在正在使用 Aquery。而且效果很好。
  • 在我的任务中调整图像大小怎么样?我只需要使用一半图像而不是整个图像。这就是为什么。
  • 好吧,只是觉得解码一个更大的图像只是为了在它之后直接画一半......
  • 您也可以受益于在输入流上使用标记/重置,首先从网络中读取几 Kb 以确定使用哪个 inSampleSize,然后在下一步中读取整个图像,截至目前您在做任何事情之前先下载整个图像。
【解决方案2】:

您必须在 Thread 中而不是在 Main thread 中执行 Bitmap 提取,在 AsyncTask 中执行此操作,否则您的应用程序将内存不足。

AsynctaskdoInBackground 方法中完成您的getBitmapFromURL() 工作

【讨论】:

  • 为什么在后台线程上进行相同的分配可以解决OOM问题?
【解决方案3】:

在您的清单中:将android:largeHeap=true 添加到您的应用程序选项卡中。

【讨论】:

猜你喜欢
  • 2012-07-27
  • 2016-08-23
  • 1970-01-01
  • 2018-10-15
  • 1970-01-01
  • 2011-09-01
  • 1970-01-01
  • 2011-10-04
  • 2013-05-08
相关资源
最近更新 更多