Android: How to resuse or limit amount of Bitmap - android

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

Related

Android App Consumes Memory

After alot of scrolling the memory goes up to:
I have simple android app that shows list if images but the memory appears to be high i'm using Picasso for loading images into the list and here is my code for loading images:
#BindingAdapter({"thumbnail_cars"})
public static void loadImage(ImageView imageView, Cars cars) {
Log.e("width ",String.valueOf((Converter.getScreenWidth()-40)*0.4));
Log.e("height ",String.valueOf(Converter.convertDpToPixel(130)));
float margins = Converter.convertDpToPixel(20);
String widthUrl = cars.getImage().replace("[w]",String.valueOf((Converter.getScreenWidth()-margins)*0.4));
String heightUrl = widthUrl.replace("[h]",String.valueOf(Converter.convertDpToPixel(130)));
imageDownloader.loadImage(heightUrl, imageView,R.drawable.ic_launcher_background);
}
Any help will be much appreciated

Memory allocation when loading a really large image on 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.

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.

Caching, Passing and Viewing bitmaps efficiently Android

I have a news feed, where news items contain images. Each news item (in the table view) obtains a image url from a server and downloads the images asynchronously / from cache.
I will use a bit of pseudocode / Java to explain my process in the simplest terms possible.
NewsItemAdapter
Map<String, Bitmap> imageCache = new HashMap<String, Bitmap>(); // init cache
String imurl = get image url from appropriate NewsObject;
Bitmap value = imageCache.get(imurl);
if (value != null) { // if bitmap is in cache
load bitmap into image view from cache;
add bitmap to NewsObject for accessing later;
}else {
execute Asynchronous bitmap download task;
}
Asynchronous bitmap download task (The reason for scaleDownBitmap() is because of OutOfMemory errors I get)
doinBackground(Void...params){
myBitmap = download bitmap from imurl;
imageCache.put(imurl, scaleDownBitmap(myBitmap)); // put bitmap into cache
return scaleDownBitmap(myBitmap);
}
onPostExecute(Bitmap result){
load result into image view;
}
MainActivity
setOnNewsItemClickListener{
intent = get intent to mainNewsScreen; //after you click on news item
intent.putExta("newsBitmap", bitmap from NewsObject); // set in NewsItemAdapter
startActivity(intent);
}
MainNewsScreen
onCreate(){
load bitmap from intent extras into image view;
}
My main problem is if I remove the scaleDownBitmap() method found here I get OutOfMemory errors.
But I am losing a lot of image quality. As you can see I haven't used bitmap.recycle() at all, I'm not entirely sure where I'd use it as I need to keep the images in memory (I would have thought).
Any idea how to make this more efficient. I'm sure this would be helpful to a lot of people attempting to create a similar app.
Consider using an lrucache instead and storing the image on disk when it falls out of the cache.
Best practice is explained here:
http://developer.android.com/intl/es/training/displaying-bitmaps/cache-bitmap.html
Consider this code as an idea, some of it copied from the above link:
...
// 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;
}
#Override
protected void entryRemoved (boolean evicted, K key, V oldValue, V newValue) {
// Save your entry to disc instead
}
};
Google has made a DiscLruCache that you can simply download and use in your project (its usage is described in the above link):
https://developer.android.com/intl/es/samples/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/DiskLruCache.html
Also don't keep an infinite amount of news / images in the view. You're going to have to remove news items as the user scrolls through them and reads them.
Consider using a Recycler view for this (along with Cards if you want a Material design feel to your app):
https://developer.android.com/intl/es/reference/android/support/v7/widget/RecyclerView.html
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="#+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager)
Cards:
https://developer.android.com/intl/es/reference/android/support/v7/widget/CardView.html
<!-- A CardView that contains a TextView -->
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/card_view"
android:layout_gravity="center"
android:layout_width="200dp"
android:layout_height="200dp"
card_view:cardCornerRadius="4dp">
<TextView
android:id="#+id/info_text"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v7.widget.CardView>
More information about combining a Recycler view with Card views:
https://developer.android.com/training/material/lists-cards.html

HashMap De-Serialization

I have a server/client app where I retrieve data from the server via Hessian/hessdroid. The data is very complex with HashMaps containing other HashMaps and images stored in byte arrays. I can display the data perfectly.
To not always query the server, I´m using a data structure as a cache. This data object I save to SD card using ObjectOutputStream when closing the app. When I restart it, I read it back to memory with an ObjectInputStream.
I´m having problems with the app only after reading the data from SD card. LogCat gives me the following output (100 times):
DEBUG/dalvikvm(4150): GetFieldID: unable to find field Ljava/util/HashMap;.loadFactor:F
and this in between the other messages:
INFO/dalvikvm-heap(4150): Grow heap (frag case) to 10.775MB for 281173-byte allocation
When the heap grows upon ~17 MB the app crashes.
I read several threads about HashMap Serialization and that there seems to be a bug when serializing between architectures, but for me the data transfer via Hessian works perfectly and I´m having the described problems only when reading the HashMaps from disk.
Any ideas?
The problem is not directly related to the HashMap Deserialization, as cyber-monk commented. There is indeed some kind of bug in Android or it´s HashMap implementation, but I don´t think it´s why the app crashes.
By now I solved the problem, using less images in the app. I had a gallery for example in which you could swipe from one image to the next in a flipper and loaded all the images at once. At a certain amount of images, there is not enough heap space.
My solution to this is, to not keep all the decoded images at once.
It´s done like this:
1) Hold the binary image data in memory (not a problem as long as the images are not that big)
2) Don´t load the binary image data into the ImageViews when creating the views of the flipper.
3) Set the binary image data into the ImageView that is displayed.
4) Keep the binary image data of the next and the last ImageView for better user experience)
5) "Unload" the ImageViews that are not displayed by setting its resource to the transparent color.
Here´s some code:
// initialize the viewFlipper by creating blank views
for (ComponentImageDto listElement : images) {
LinearLayout view = renderView();
flipper.addView(view);
}
showImage(flipper.getCurrentView());
renderView() just returns a LinearLayout containing an ImageView
Then I wrote some methods to show the next/previous image in which I set the binary data to the ImageView:
private void showNextElement() {
// show next flipper view
flipper.showNext();
// get current view
int displayedChild = flipper.getDisplayedChild();
View currentView = flipper.getCurrentView();
// load the binary data
showImage(currentView);
// get the next to last view index (if keeping max. 3 images at a time in memory)
int otherChild = (displayedChild - 2);
if (otherChild < 0) {
otherChild = otherChild + flipper.getChildCount();
}
// .. and remove it
removeImage(flipper.getChildAt(otherChild));
}
private void showPreviousElement() {
flipper.showPrevious();
int displayedChild = flipper.getDisplayedChild();
View currentView = flipper.getCurrentView();
showImage(currentView);
setTitle((CharSequence) currentView.getTag());
int otherChild = (displayedChild + 2) % flipper.getChildCount();
removeImage(flipper.getChildAt(otherChild));
}
private void removeImage(View view) {
ImageView imageView = (ImageView) view.findViewById(R.id.gallery_image);
if (imageView != null) {
imageView.setImageResource(R.color.transparent);
System.gc();
}
}
private void showImage(View view) {
ImageView imageView = (ImageView) view.findViewById(R.id.gallery_image);
if (imageView != null) {
bm = BitmapHelper.decodeByteArray(images.get(flipper.getDisplayedChild()).getImage().getBinaryObject());
imageView.setImageBitmap(bm);
}
}
To furtherly improve memory handling I´m using some code in BitmapHelper class that I found on stackoverflow, that helps to save memory for images by reducing their size.

Categories

Resources