【问题标题】:Memory allocation when loading a really large image on Android在 Android 上加载非常大的图像时的内存分配
【发布时间】:2017-05-02 09:34:47
【问题描述】:

在加载位图时,我需要弄清楚在没有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”这部分对我来说也很奇怪。为什么我会看到这些数字?

【问题讨论】:

标签: android bitmap out-of-memory


【解决方案1】:

第一件事,
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(或任何视图)的确切尺寸,这将防止图像像素化,也将节省额外的内存消耗。 (你也可以做一些小的计算来保持图像的纵横比)

【讨论】:

  • 首先,您对相应 PNG 的大小是正确的。它是 116 965 828 字节(磁盘上的 117 MB)。我刚刚将JPEG保存为PNG文件。确实是 10 倍。其次,如果加载图像的大小是通过它的高度乘以其宽度然后乘以 4 来计算的,那么对于我的图像,结果是 10800 * 5400 * 4 = 233 280 000 字节。这几乎是我在日志中看到的:未能分配 233280012 字节。不确定,可能是巧合
【解决方案2】:

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

【讨论】:

  • 只是为了澄清一点。 Glide 不会自动处理内存问题。很少滥用 Glide 会导致巨大的 OOM 问题。您必须以 Glide 和其他图像正常工作的方式设计您的应用程序。是的,你也必须有效地使用 Glide builder。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-04-23
  • 1970-01-01
  • 1970-01-01
  • 2015-07-06
  • 2016-02-27
  • 2011-05-27
  • 1970-01-01
相关资源
最近更新 更多