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.
Related
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);
I am working on an app that uses a Recyclerview to display mp3 files, providing its cover art image along with other info. It works but is slow once it starts dealing with a dozen or more cover arts to retrieve, as I am currently doing this from the id3 on the main thread, which I know is not a good idea.
Ideally, I would work with placeholders so that the images can be added as they become available. I've been looking into moving the retrieval to a background thread and have looked at different options: AsyncTask, Service, WorkManager. AsyncTask seems not to be the way to go as I face memory leaks (I need context to retrieve the cover art through MetadataRetriever). So I am leaning away from that. Yet I am struggling to figure out which approach is best in my case.
From what I understand I need to find an approach that allows multithreading and also a means to cancel the retrieval in case the user has already moved on (scrolling or navigating away). I am already using Glide, which I understand should help with the caching.
I know I could rework the whole approach and provide the cover art as images separately, but that seems a last resort to me, as I would rather not weigh down the app with even more data.
The current version of the app is here (please note it will not run as I cannot openly divulge certain aspects). I am retrieving the cover art as follows (on the main thread):
static public Bitmap getCoverArt(Uri medUri, Context ctxt) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(ctxt, medUri);
byte[] data = mmr.getEmbeddedPicture();
if (data != null) {
return BitmapFactory.decodeByteArray(data, 0, data.length);
} else {
return null;
}
}
I've found many examples with AsyncTask or just keeping the MetaDataRetriever on the main thread, but have yet to find an example that enables a dozen or more cover arts to be retrieved without slowing down the main thread. I would appreciate any help and pointers.
It turns out it does work with AsyncTask, as long as it is not a class onto itself but setup and called from a class with context. Here is a whittled down version of my approach (I am calling this from within my Adapter.):
//set up titles and placeholder image so we needn't wait on the image to load
titleTv.setText(selectedMed.getTitle());
subtitleTv.setText(selectedMed.getSubtitle());
imageIv.setImageResource(R.drawable.ic_launcher_foreground);
imageIv.setAlpha((float) 0.2);
final long[] duration = new long[1];
//a Caching system that helps reduce the amount of loading needed. See: https://github.com/cbonan/BitmapFun?files=1
if (lruCacheManager.getBitmapFromMemCache(selectedMed.getId() + position) != null) {
//is there an earlier cached image to reuse? imageIv.setImageBitmap(lruCacheManager.getBitmapFromMemCache(selectedMed.getId() + position));
imageIv.setAlpha((float) 1.0);
titleTv.setVisibility(View.GONE);
subtitleTv.setVisibility(View.GONE);
} else {
//time to load and show the image. For good measure, the duration is also queried, as this also needs the setDataSource which causes slow down
new AsyncTask<Uri, Void, Bitmap>() {
#Override
protected Bitmap doInBackground(Uri... uris) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(ctxt, medUri);
byte[] data = mmr.getEmbeddedPicture();
Log.v(TAG, "async data: " + Arrays.toString(data));
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
duration[0] = Long.parseLong(durationStr);
if (data != null) {
InputStream is = new ByteArrayInputStream(mmr.getEmbeddedPicture());
return BitmapFactory.decodeStream(is);
} else {
return null;
}
}
#Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
durationTv.setVisibility(View.VISIBLE);
durationTv.setText(getDisplayTime(duration[0], false));
if (bitmap != null) {
imageIv.setImageBitmap(bitmap);
imageIv.setAlpha((float) 1.0);
titleTv.setVisibility(View.GONE);
subtitleTv.setVisibility(View.GONE);
} else {
titleTv.setVisibility(View.VISIBLE);
subtitleTv.setVisibility(View.VISIBLE);
}
lruCacheManager.addBitmapToMemCache(bitmap, selectedMed.getId() + position);
}
}.execute(medUri);
}
I have tried working with Glide for the caching, but I haven't been able to link the showing/hiding of the TextViews to whether there is a bitmap. In a way though, this is sleeker as I don't need to load the bulk of the Glide-library. So I am happy with this for now.
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
I'm trying to free memory when I don't need it any more but that doesn't seem to be easy. I create a ListView containing some images (Bitmaps). Every time I do that, the memory monitor shows increase of memory used by my app. This is how I do it:
private ArrayList<ImgMdl> populateList(){
ArrayList<ImgMdl> list = new ArrayList<>();
File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + settingsFileDir + "/" + imgLogFileDir);
if(folder.exists()) {
File[] listOfFiles = folder.listFiles();
listOfFiles = sortFileList(listOfFiles);
for (int i = 0; i < listOfFiles.length; i++) {
String imgFilePath = listOfFiles[i].toString();
Bitmap bmp = BitmapFactory.decodeFile(imgFilePath);
ImgMdl imgMdl = new ImgMdl();
imgMdl.set_itm_txt(getDateString(listOfFiles[i].getName()));
imgMdl.set_itm_img(bmp);
list.add(imgMdl);
}
}
return list;
}
public class MainGate_ImgMdl {
private String itm_txt;
private Bitmap itm_img;
public String get_itm_txt() {
return itm_txt;
}
public void set_itm_txt(String itm_txt) {
this.itm_txt = itm_txt;
}
public Bitmap get_itm_img() {
return itm_img;
}
public void set_itm_img(Bitmap itm_img) {
this.itm_img = itm_img;
}
}
If I open the activity containing that list several times, I'll sometime get an outOfMemoryException. How can I free memory if I don't need it any more? finish() doesn't help.
First, do not load images until they are needed. Suppose that you have 100 images in that directory. Most likely, the user cannot see all 100 on the screen — they would have to scroll to do that. If the user does not scroll, you have loaded the unseen images for no reason.
Second, do not load images that you already loaded. If you "open the activity containing that list several times", and you are loading all of the images each time, that is a waste, as you loaded many of those images previously.
Overall, do not load images yourself. There are plenty of image-loading libraries that are available, such as Picasso and Glide. A decent image-loading library will have a configurable cache (addressing my second point) and know how to load images on-demand in the background, even from rows in a ListView or RecyclerView (addressing my first point).
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