Memory allocation when loading a really large image on Android - android

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?

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)

You should use Glide. Its a library endorsed by Google. It automatically handles all the memory management and also the image loading functionality.

Related

Android: How to resuse or limit amount of Bitmap

I do have an app that shows images and some other text info from firebase-Realtime,
there is a problem with my app ram usage that it can easily cross over 400+mb
so i used profiler to check and solved every MemoryLeaks my app have, now when i checked the Android studio profiler i found that there is a ton of Bitmaps that are using loads of memory.
AlSO Iam using Picasso to load images into Recycleview from within Adapter
also using Glide sometimes
so i want to knew is it possible to solve these bitmap memory usage or is it possible to to limit number of bitmap that can be used, cuz every time a new image are shown the app would make a new bitmap and uses more Ram.
Adapter code to load Images into recyclerview:
public class HomeScreenWorkViewHolder extends RecyclerView.ViewHolder {
View view;
DatabaseReference likeReference;
public ImageView like_btn;
public TextView like_text;
public HomeScreenWorkViewHolder(#NonNull View itemView) {
super(itemView);
like_btn = itemView.findViewById(R.id.like_btn);
like_text = itemView.findViewById(R.id.like_text);
view = itemView;
}
public HomeScreenWorkViewHolder(#NonNull View itemView, OnItemClick callBack) {
super(itemView);
like_btn = itemView.findViewById(R.id.like_btn);
like_text = itemView.findViewById(R.id.like_text);
view = itemView;
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callBack.onItemClicked(getAdapterPosition());
}
});
}
public void setdetails( String name, String image, String description, String location) {
TextView mtitletv = view.findViewById(R.id.product_layout_name);
TextView mdesrcriptiontv = view.findViewById(R.id.product_layout_description);
TextView mlocationtv = view.findViewById(R.id.product_layout_location);
ImageView mImagetv = view.findViewById(R.id.product_layout_image);
mtitletv.setText(name);
mdesrcriptiontv.setText(description);
mlocationtv.setText(location);
Picasso.get().load(image).placeholder(R.drawable.ic_baseline_cloud_download_24).into(mImagetv);
}
Sounds like you are either keeping references to the bitmap around. If that is not the case, but you are trying to keep references in memory, but reduce memory footprint with changing the sample size.
Sampling size can be thought of as this: If you have a 1000x1000 image by 8 bits per pixel..
You have a 1,000,000 bytes of image loaded into memory. Lets say you only needed a thumbnail of 100x100. You could then change the sampling size to 10, and it would read every 10th pixel from the file, building a newer / smaller memory footprint image. This would then Go from 1,000,000 bytes to 10,000 bytes.
Try the answer on this SO question..
Strange OutOfMemory issue while loading an image to a Bitmap object
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

Picasso - how to get the real image size?

I load an image from URLusing Picasso library. I want to get the real image size, but I can only get the image size in memory:
Picasso.with(this)
.load(imageUrl)
.error(R.drawable.no_image)
.into(photoView, new Callback() {
#Override
public void onSuccess() {
Bitmap bitmap = ((BitmapDrawable)photoView.getDrawable()).getBitmap();
textImageDetail.setText(bitmap.getByteCount());// image size on memory, not actual size of the file
}
#Override
public void onError() { }
});
How to get the size of the loaded image? I think it is stored somewhere in a cache, but I do not know how to access the image file.
Update
Sorry for my bad English, maybe I asked the wrong question. I need to get the image size (128 kb, 2 MB, etc.). NOT the image resolution (800x600, etc.)
You could first get the actual Bitmap image that is getting loaded, and then find the dimensions of that. This has to be run in an asynchronous method like AsyncTask because downloading the image is synchronous. Here is an example:
Bitmap downloadedImage = Picasso.with(this).load(imageUrl).get();
int width = downloadedImage.getWidth();
int height = downloadedImage.getHeight();
If you want to get the actual image size in bytes of the Bitmap, just use
// In bytes
int bitmapSize = downloadedImage.getByteCount();
// In kilobytes
double kbBitmapSize = downloadedImage.getByteCount() / 1000;
Replace the imageUrl with whatever URL you want to use. Hope it helps!
I know this question is old, but I stepped here for an answer and found none.
I found a solution that worked with me using OkHttpClient.
You can fetch the header information only, using OkHttpClient and get the content length without downloading the image.
OkHttpClient httpClient = new OkHttpClient();
Request request = new Request.Builder().url(imageURL).head().build();
Response response = null;
try {
response = httpClient.newCall(request).execute();
String contentLength = response.header("content-length");
int size = Integer.parseInt(contentLength);
} catch (IOException e ) {
if (response!=null) {
response.close();
}
}
Notes:
the above code performs a network call, it should be executed on a background thread.
size is returned in Bytes, you can divide by 1000 if you want it in KB.
this may not work with large files.
Beware, casting to integer could bypass the integer's max value.

Out of memory loading imageViews

I have a scrollview which has small thumbnails of images loaded via AsyncTask and throws the image URL unto a imageView.
They are added dynamically and then on top, is a main imageView which holds the image of the thumbnail you clicked.
Everything runs great until you have about 10+ images in the thumbnails...
I am loading the mainImage url via the same way as the thumbnails, so when they click an image in the thumb, it loads it up top.
I am recycling the bitmap in the method itself, but it seems to be running out of memory and crashing when loading more than 10 images (thumbnails load ok, but crashes when i click to load the main image)
any help appreciated
this is the code i am using the load the images (thumbnails + main):
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
ImageView bmImage;
public DownloadImageTask(ImageView bmImage) {
this.bmImage = bmImage;
}
protected Bitmap doInBackground(String... urls) {
String urldisplay = urls[0];
Bitmap mIcon11 = null;
try {
InputStream in = new java.net.URL(urldisplay).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
return mIcon11;
}
protected void onPostExecute(Bitmap result) {
bmImage.setImageBitmap(result);
}
}
just implement this on ur image ... it will reduce ur image by 4 times
public static Bitmap getImage(byte[] image) {
BitmapFactory.Options config = new BitmapFactory.Options();
config.inPreferredConfig = Bitmap.Config.RGB_565;
config.inSampleSize = 4;
return BitmapFactory.decodeByteArray(image, 0, image.length,config);
}
You should read the two following Android official tutorials. They will teach you how to load large bitmap efficiently, and they provide working code samples that we use in our production apps
Displaying Bitmaps Efficiently
Loading Large Bitmaps Efficiently
you are not closing the input stream. So all the time when images urls executed input stream objects created which are expensive and causing you OutofMemoryError. add below code after catch block.
in.close();
in = null;
You probably have to work with some sort of cache strategy for the bitmaps, try having a look on the library Universal Image Loader, it works very well for what you need
Recycling bitmaps by yourself should only be done when you know for sure that it won't be used anymore.
Try to resize the bitmap to a smaller size (with a factor like 1.5 example: width=widt*1.5)
then strech(fit xy or whatever) in your view.it will lower the quality of the image due to the factor you decide. it is a quick dirty way. also making bit map rgb like Niun Goia mentions works well.
You shouldn't be using a ScrollView for this. You should be using a ListView. When you use a ScrollView (with a LinearLayout or something like that inside it), all the elements in the list (no matter how big it is) will try to be loaded immediately. Try a ListView instead (which is made for that exact reason, it only loads in memory the ui elements of the rows that are visible).
You can use android:largeHeap="true" to request a larger heap size
answer
I use this..
Options opt = new Options();
opt.inPurgeable = true;
opt.inInputShareable = true;
opt.inPreferredConfig = Bitmap.Config.RGB_565;

java.lang.OutOfMemoryError: bitmap size exceeds VM budget in ListView and lazy loading images

I have a listview that probably has infinite items loaded on scrolling infinitely.
Each item in list view has one or two images which I'm lazy loading.
Everything works great but when I scroll for really long it crashes with this in log cat
08-07 15:26:25.231: E/AndroidRuntime(30979): FATAL EXCEPTION: Thread-60
08-07 15:26:25.231: E/AndroidRuntime(30979): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
08-07 15:26:25.231: E/AndroidRuntime(30979): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
08-07 15:26:25.231: E/AndroidRuntime(30979): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:493)
08-07 15:26:25.231: E/AndroidRuntime(30979): at com.test.android.helper.LazyImageLoader.decodeFile(LazyImageLoader.java:171)
08-07 15:26:25.231: E/AndroidRuntime(30979): at com.test.android.helper.LazyImageLoader.getBitmap(LazyImageLoader.java:112)
08-07 15:26:25.231: E/AndroidRuntime(30979): at com.test.android.helper.LazyImageLoader.access$2(LazyImageLoader.java:106)
08-07 15:26:25.231: E/AndroidRuntime(30979): at com.test.android.helper.LazyImageLoader$ImageLoader.run(LazyImageLoader.java:197)
In my lazy image loader I am storing bitmaps in a WeakHashMap. So garbage collector should collect the bitmaps right?
My lazy imageloader works something like this.
I call displayImage() from my adapter with url and a reference to imageview
public void displayImage(String url, ImageView imageView, int defaultImageResourceId){
latestImageMetaData.put(imageView, url);
if(weakhashmapcache.containsKey(url)){
imageView.setImageBitmap(weakhashmapcache.get(url));
}
else{
enqueueImage(url, imageView, defaultImageResourceId);
imageView.setImageResource(defaultImageResourceId);
}
}
So if I find the image in cache, I set it directly, otherwise I queue it with function enqueueImage().
private void enqueueImage(String url, ImageView imageView, int defaultImageResourceId){
Image image = new Image(url, imageView, defaultImageResourceId);
downloadqueue.add(image);
// downloadQueue is a blocking queue which waits for images to be added
//If the queue is about to get full then delete the elements that are ahead in the queue as they are anyway not visible
Iterator iterator = downloadQueue.iterator();
while(iterator.hasNext() && downloadQueue.remainingCapacity() < 80){
downloadQueue.remove(iterator.next());
}
}
And my image loader thread is this -
class ImageLoader extends Thread {
public void run() {
Image firstImageInQueue;
try {
while((firstImageInQueue = downloadQueue.take()) != SHUTDOWN_TOKEN)
{
Bitmap imageBitmap = getBitmap(firstImageInQueue.url);
if(imageBitmap != null){
weakhashmap.put(firstImageInQueue.url, imageBitmap);
BitmapDisplayer displayer = new BitmapDisplayer(imageBitmap, firstImageInQueue.imageView, firstImageInQueue.url, firstImageInQueue.defaultImageResourceId);
Activity activity = (Activity)firstImageInQueue.imageView.getContext();
activity.runOnUiThread(displayer);
}
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
imageLoaderTerminated = true;
}
}
}
getBitmap() just fetches image from url scales and decodes it into a Bitmap object. BitmapDisplayer is just a Runnable which does the setting of image to imageview on UI thread.
What am I doing wrong?
It has been a nightmare and after days & nights of research here are few points that may be useful to others.
Don't store all the Bitmaps in cache. Keep it swapping between Disk cache and Memory Cache. Number of bitmaps you store can depend on the heap limit that you get by calling
int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
.getMemoryClass();
I used a LruCache instead of WeakHashMap cache. LruCache is available in the support package. It is very easy to replace your existing WeakHashMap implementation with LruCache. Android also has a beautiful documentation on Caching Bitmaps.
Jake Wharton's DiskLruCache is a great way to manage Disk Cache.
Don't download huge bitmaps if you do not need it. Try to get a size that's just good enough to fit your need.
Using BitmapFactory.Options you can make some trade offs with image quality to hold more images in memory cache.
If you think there's anything more we could do, please add.
Try the Universal Image Loader
It's an open-source project and they have blog posts about how to use it. I've already used it in several projects and I'm not having problem even in big long lists.
Enjoy!!

android bitmap caching

I would like to load images into a gallery view from a url?
I first make them a bitmap using this.
URL aURL = new URL(myRemoteImages[position]);
URLConnection conn = aURL.openConnection();
conn.setUseCaches(true);
conn.connect();
Object response = conn.getContent();
if (response instanceof Bitmap) {
Bitmap bm = (Bitmap)response;
InputStream is = conn.getInputStream();
/* Buffered is always good for a performance plus. */
BufferedInputStream bis = new BufferedInputStream(is);
/* Decode url-data to a bitmap. */
bm = BitmapFactory.decodeStream(bis);
bis.close();
is.close();
Log.v(imageUrl, "Retrieving image");
/* Apply the Bitmap to the ImageView that will be returned. */
i.setImageBitmap(bm);
How could I go about caching this bitmap? So when the user swipes the screen it doesn't reload over and over?
EDIT: I CALL getImage() to retreive the text url for each url.
i use both of these in a asyncTask. preExecute i call getImage()
and doInBackground i set the gallery to the imageAdapter.
private class MyTask extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... arg0) {try {
getImages();
Log.v("MyTask", "Image 1 retreived");
getImage2();
Log.v("MyTask", "Image 2 retreived");
getImage3();
Log.v("MyTask", "Image 3 retreived");
getImage4();
Log.v("MyTask", "Image 4 retreived");
} catch (IOException e) {
Log.e("MainMenu retreive image", "Image Retreival failed");
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void notUsed){
((Gallery) findViewById(R.id.gallery))
.setAdapter(new ImageAdapter(MainMenu.this));
}
}
EDIT: getView() method
public View getView(int position, View convertView, ViewGroup parent) {
ImageView i = new ImageView(this.myContext);
try {
URL aURL = new URL(myRemoteImages[position]);
URLConnection conn = aURL.openConnection();
conn.setUseCaches(true);
conn.connect();
Object response = conn.getContent();
if (response instanceof Bitmap) {
Bitmap bm = (Bitmap)response;
You could store your images on the SDCard and on the launch of your application you need to initialize a component that keeps a HashMap<String,Bitmap> and initialize the map with the contents of a folder from the SDCard.
When you will need an image, you will first check if your HashMap contains the key of that image, let say myMap.contains(myFileName) and if it does you will fetch the image from the map, and if the image is not contained in your map you will need to download it, store id on the SDCard and put in in your map.
I'm not sure if this is the best solution, since if you have a large number of Bitmaps your application can run out of resources. Also I think storing Drawable instead of Bitmap will be less memory consuming.
EDIT:For your problem you need create a custom class that has a member Drawable and execute the URLConnection just when you first create your objects. After that in the getView() method you will just use myObj.getMyDrawable() to access the drawable for that specific object.
Since Android 4 it is possible to cache HTTP responses directly by the HttpUrlConnection. See this article: http://practicaldroid.blogspot.de/2013/01/utilizing-http-response-cache.html
Caching images on android is an level oriented task:
Generally at Caching at two levels:
Runtime Heap memory in a form key-value, where key being a identifier for image and value is object of bitmap.(Refer Here)
Most optimised Way for implementing it is LRUCache:
Which Basically maintains a LinkedList for recently accessed items where dump the items accessed at earliest due to memory limitation.
As this backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.
private LruCache<String, Bitmap> mMemoryCache;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
Disk Storage: As memory has limited storage with restricted lifecycle.
Memory Cache is good for speeding up in accessing recently viewed images but you can not rely on this for images available on this cache.
UIComponents with indefinite and large data set can easily fill up memory and resulting in loss of images.
Memory cache may get effected in situations like going in background while on call.
Disk Cache can help you to make images persist for longer.
One of it's Optimized way of using it is DiskLruCache
So When you look up for a bitmap in memory cache is result is nil you can try to access it from disk cache and incase you don't find it here too and then just load it from internet.
For Implementation Refer here

Categories

Resources