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

Memory allocation when loading a really large image on Android

我需要弄清楚在加载位图时我可以分配多少内存而没有 OutOfMemoryError。我决定从文件中加载 really 大图像。这是它的 URL: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”部分对我来说也很困难运行。为什么我会看到这些数字?

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

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

所以图片的实际大小基本上是其分辨率乘以 4 (A-R-G-B)

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

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

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