问题
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?
回答1:
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)
回答2:
You should use Glide. Its a library endorsed by Google. It automatically handles all the memory management and also the image loading functionality.
来源:https://stackoverflow.com/questions/43734564/memory-allocation-when-loading-a-really-large-image-on-android