在Android上加载非常大的图像时的内存分配

时间:2017-05-02 09:34:47

标签: android bitmap out-of-memory

在加载位图时,我需要计算出可以在没有OutOfMemoryError的情况下分配的大量内存。我决定从文件中加载真正的大图像。以下是其网址:https://upload.wikimedia.org/wikipedia/commons/3/3d/LARGE_elevation.jpg

图像大小为14 488 498字节(磁盘上为14,5 MB)。这是我的代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String LOG_TAG = "LargeBitmapLargeBitmap";

    private ActivityManager mActivityManager;
    private ImageView mImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        mImageView = (ImageView) findViewById(R.id.image);
        findViewById(R.id.get_mem_info).setOnClickListener(this);
        findViewById(R.id.load_bitmap).setOnClickListener(this);

    }

    @Override
    public void onClick(final View v) {
        switch (v.getId()) {
            case R.id.get_mem_info:
                Runtime rt = Runtime.getRuntime();
                Log.v(LOG_TAG, "maxMemory: " + rt.maxMemory());
                Log.v(LOG_TAG, "freeMemory: " + rt.freeMemory());
                Log.v(LOG_TAG, "totalMemory: " + rt.totalMemory());
                Log.v(LOG_TAG, "mActivityManager.getMemoryClass(): " + mActivityManager.getMemoryClass());
                break;
            case R.id.load_bitmap:
                new ImageLoadingTask(mImageView).execute();
                break;
        }
    }

    // I don't handle the screen rotation here since it's a test app
    private static class ImageLoadingTask extends AsyncTask<Void, Void, Bitmap>  {

        private ImageView mImageView;

        ImageLoadingTask(ImageView imageView) {
            mImageView = imageView;
        }

        @Override
        protected Bitmap doInBackground(final Void... params) {
            String path = Environment.getExternalStorageDirectory().getAbsolutePath()
                    + File.separator
                    + "LARGE_elevation.jpg";
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory.decodeFile(path);
            } catch (OutOfMemoryError error) {
                Log.e(LOG_TAG, "Failed to load the large bitmap", error);
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(final Bitmap bitmap) {
            super.onPostExecute(bitmap);
            mImageView.setImageBitmap(bitmap);
        }
    }
}

我编译了上面的代码并在我的Nexus 5上运行了。然后我按下了get_mem_info按钮并获得了以下输出:

05-02 12:07:35.872 28053-28053/com.sample V/LargeBitmapLargeBitmap: maxMemory: 201326592
05-02 12:07:35.872 28053-28053/com.sample V/LargeBitmapLargeBitmap: freeMemory: 4991056
05-02 12:07:35.872 28053-28053/com.sample V/LargeBitmapLargeBitmap: totalMemory: 20733248
05-02 12:07:35.873 28053-28053/com.sample V/LargeBitmapLargeBitmap: mActivityManager.getMemoryClass(): 192

这意味着我的应用程序可用的堆内存为192 MB。但是当我按下load_bitmap按钮时,图像没有加载,我得到了OutOfMemoryError

05-02 12:07:38.712 28053-29533/com.sample E/LargeBitmapLargeBitmap: Failed to load the large bitmap
                                                                    java.lang.OutOfMemoryError: Failed to allocate a 233280012 byte allocation with 10567360 free bytes and 176MB until OOM
                                                                        at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
                                                                        at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
                                                                        at android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.java:635)
                                                                        at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:611)
                                                                        at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:391)
                                                                        at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:417)
                                                                        at com.sample.MainActivity$ImageLoadingTask.doInBackground(MainActivity.java:70)
                                                                        at com.sample.MainActivity$ImageLoadingTask.doInBackground(MainActivity.java:55)

为什么会这样?为什么系统需要分配233280012字节(约220MB)?部分“10567360免费字节和176MB直到OOM”对我来说也很奇怪。为什么我会看到这些数字?

2 个答案:

答案 0 :(得分:3)

首先,第一件事,
14.5 MB是 JPEG 的大小,但不是实际图像的大小 同样地,尝试将图像重新保存为png,您将看到该大小增加了10倍,即它甚至可能达到150 MB

您必须记住的一件事是,这些图像压缩为JPEG或PNG。
但是当这些图像加载到imageview左右时,图像的每个都会被解压缩并占用RAM中的内存。

因此,图像的实际尺寸基本上是其分辨率乘以4(A-R-G-B)

编辑 - 如何处理这些图像?
首先,使用Glide(或Picasso)加载图像,而不是为此编写自己的AsyncTask。

在Glide中有一种名为override(int width, int height)的方法,可在下载时调整图像大小
理想情况下,宽度和高度应该是ImageView(或任何视图)的精确尺寸,这将防止图像像素化,并且还将节省额外的内存消耗。 (您可以随时进行小型计算以保持图像宽高比)

答案 1 :(得分:-2)

您应该使用Glide。它是由Google支持的图书馆。它会自动处理所有内存管理以及图像加载功能。