Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I need to figure out much memory I can allocate without having an OutOfMemoryError when loading a bitmap. I decided to load a really large image from a file. Here is its URL: https://upload.wikimedia.org/wikipedia/commons/3/3d/LARGE_elevation.jpg

The image size is 14?488?498 bytes (14,5 MB on disk). And here is my code:

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

I compiled the code above and ran it on my Nexus 5. Then I pressed the get_mem_info button and got the following output:

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

which means that the heap memory available for my application is 192 MB. But when I pressed the load_bitmap button, the image wasn't loaded, and I got an 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)

Why did that happen? Why did the system need to allocate 233280012 bytes (about 220MB)? The part "10567360 free bytes and 176MB until OOM" looks strange to me as well. Why do I see these numbers?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
170 views
Welcome To Ask or Share your Answers For Others

1 Answer

First things first,
14.5 MB is the size of the JPEG but not the size of your actual image.
Similarly, try to resave the image as png, you will see that size is increased by factors of 10, i.e. it might even reach 150 MB

One thing that you must keep in mind is that these images are compressed into JPEG or PNG.
But when these images are loaded into imageview or so, each Bit of the image is decompressed and occupies a memory in RAM.

So the actual size of your image is basically its resolution multiplied by 4 (A-R-G-B)

EDIT - How to handle these images then?
Firstly, use Glide (or Picasso) to load the images, instead of writing own AsyncTask for this.

In Glide there is a method called override(int width, int height) which will resize the image while downloading.
Ideally width and height should be the exact dimension of the ImageView (or any view), this will prevent the image from pixellating and also it will save the additional consumption of the memory. (you can always do minor calculations to retain image aspect ratio too)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...