View Pager with Universal Image Loader Out of Memory Error - android

I am not really sure if a ViewPager with Universal Image Loader can/should be used as an alternate for a gallery like interface since I have run into an Out of Memory error while loading images from SD Card and viewing them in full screen mode. No matter what the number, it works all fine with a GridView but while viewing the images in the View Pager, each bitmap keeps eating up a lot of memory and after 10 or so images, it gives the out of memory error.
I have seen almost all the questions that have been posted here related to the Out of Memory Error while working with the Universal Image Loader and in each one of them, there has been a configurations error as the cause.
I dont know if I am using the wrong configurations or what but I have wasted a lot of time on it and am kind of stuck, any help/advice would be appreciated.
The configurations for the ImageLoader:
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
.memoryCache(new WeakMemoryCache())
.denyCacheImageMultipleSizesInMemory()
.discCacheFileNameGenerator(new Md5FileNameGenerator())
.imageDownloader(new ExtendedImageDownloader(getApplicationContext()))
.tasksProcessingOrder(QueueProcessingType.LIFO)
// .enableLogging() // Not necessary in common
.build();
The Display Image Options are:
options = new DisplayImageOptions.Builder()
.showImageForEmptyUri(R.drawable.image_for_empty_url)
.resetViewBeforeLoading()
.imageScaleType(ImageScaleType.IN_SAMPLE_INT)
.bitmapConfig(Bitmap.Config.RGB_565)
.displayer(new FadeInBitmapDisplayer(300))
.build();
I am using the example project that was given with the library but those settings wont work either, it just crashes after some time. My guess is that there is a specific callback where I have to recycle bitmaps from the views from that are not visible.
EDIT: I know its a memory leak, the views that are not visible are destroyed when they should be but the memory is not released as it should. Heres the implementation of the destroyItem callback, followed the tips given in different questions but still cant find the memory leak.
#Override
public void destroyItem(View container, int position, Object object) {
// ((ViewPager) container).removeView((View) object);
Log.d("DESTROY", "destroying view at position " + position);
View view = (View)object;
((ViewPager) container).removeView(view);
view = null;
}

It's probably not the best implementation to solve it, but it worked for me. Removing the ImageViews is not enough, so I decided to recycle bitmaps in 'destroyItem':
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;
ImageView imageView = (ImageView) view.findViewById(R.id.image);
if (imageView != null) {
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
bitmap.recycle();
bitmap = null;
}
((ViewPager) container).removeView(view);
view = null;
}
This does not clean the last 3 active pages when you leave the activity, although I hope that GC takes care of them.

Try to apply next suggestions:
Use ImageScaleType.EXACTLY
Enable caching on disc (in display options).
Finally try to use .discCacheExtraOptions(maxImageWidthForDiscCache, maxImageHeightForDiscCache, CompressFormat.PNG, 0);

Just posting this because this question is coming up on Google when searching for UIL and OOP. I had OOP problems no matter what configuration, what solved all my problems were the two classes RecyclingImageView and RecyclingBitmapDrawable from this sample project.

I also used the same library and had same error. As solution, i created a sparseArray to keep photoView instances. And use it like this:
private SparseArray<PhotoView> photoViewHolder;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
photoViewHolder = new SparseArray<PhotoView>();
...
}
private class GalleryPagerAdapter extends PagerAdapter {
#Override
public View instantiateItem(ViewGroup container, int position) {
PhotoView photoView = new PhotoView(container.getContext());
ImageHolder holder = new ImageHolder();
holder.position = position;
holder.loaded = false;
photoView.setTag(holder);
photoViewHolder.put(position, photoView);
// I used LazyList loading
loader.DisplayImage(items.get(position), photoView);
// Now just add PhotoView to ViewPager and return it
container.addView(photoView, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
return photoView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
photoViewHolder.remove(position);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
And to handle viewPager's listener:
pager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageScrollStateChanged(int position) {
}
#Override
public void onPageScrolled(int position, float arg1, int arg2) {
}
#Override
public void onPageSelected(int position) {
if(photoViewHolder.get(position) != null) {
ImageHolder holder = (ImageHolder)photoViewHolder.get(position).getTag();
// Do something...
}
}
});
Hope this helps...

I used kutothe's implementation from github issues page.

I had this problem when simply setting Uri to ImageView using: iv.setImageURI(Uri.fromFile(imgFile));
I had the same problem with Universal Image Loader, and I even looked for other Image Loaders out there, and found another good one called "Picasso", but it also had the same problem.
So what worked for me is using GestureImageView and setting gesture-image:recycle to true through XML, and load the images with the following code:
Drawable yourDrawable = null;
try {
InputStream inputStream = getActivity().getContentResolver().openInputStream(Uri.fromFile(img));
yourDrawable = Drawable.createFromStream(inputStream, Uri.fromFile(img).toString() );
inputStream.close();
} catch (FileNotFoundException e) {
yourDrawable = getResources().getDrawable(R.drawable.ic_launcher);
} catch (IOException e) {
e.printStackTrace();
}
if (yourDrawable != null)
iv.setImageDrawable(yourDrawable);
the reason it was crashing and giving OOM error is that the bitmaps aren't recycled when the image aren't displayed on the screen anymore, hence a memory leak occurs.
If there is another way to recycle the bitmap in the normal ImageView, that would be a better solution.
Hope I helped.

I know it's late, but maybe my answer will save someone's time. After hours and hours of trying to solve this issue (with almost every answer found on stack overflow) I finally solved it with Fresco image library. It'a a lib written by Facebook and it's primary goal is to use memory in efficient way. It's really great and my Out Of Memory Error disappeared. I highly recommend using it.
http://frescolib.org/

Related

Android ViewPager - OutOfMemory Exception using Glide (on phones and on TV)

I'm using ViewPager in my app for showing pictures (from camera, etc. so it can be large pictures). Also I'm using Glide library for loading pictures from Uri to ImageView. This is my PagerAdapter:
public class GalleryPagerAdapter extends PagerAdapter {
private List<FileMetaData> mPhotos = null;
private Context _context;
private LayoutInflater _inflater;
public GalleryPagerAdapter(Context context, List<FileMetaData> files) {
_context = context;
mPhotos = files;
_inflater = (LayoutInflater) _context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((ImageView) object);
}
#Override
public int getCount() {
return mPhotos.size();
}
#Override
public Object instantiateItem(ViewGroup container, final int position) {
View itemView = _inflater.inflate(R.layout.photo_pager_item, container, false);
container.addView(itemView);
ImageView imageView = (ImageView) itemView.findViewById(R.id.imageViewPhotoDetail);
Glide.with(_context).load(mPhotos.get(position).getUri()).placeholder(R.drawable.placeholder_image)
/*.skipMemoryCache(true).*/.diskCacheStrategy(DiskCacheStrategy.NONE).into(imageView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
ImageView i = (ImageView) object;
container.removeView(i);
}
}
Layout photo_pager_item is simple:
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/imageViewPhotoDetail"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And setOffscreenPageLimit is set to 1 (by default). My problem is that I'm getting OutOfMemory Exception. But I don't know how to solve it. What should I change? How can I recycle images loaded by Glide? And I need to make it work also on TV (big screen).
My problem is that I'm getting OutOfMemory Exception. But I don't know
how to solve it. What should I change? How can I recycle images loaded
by Glide? And I need to make it work also on TV (big screen).
There is nothing wrong with your implementation. if you are getting OutOfMemory Exception This usually comes when your memory buffer size becomes full let say in case if you are displaying large size images in devices which are having low memory.
Possible solutions can be :
1. As you are saying you are loading the image view from the URL. Downloading 1080*1920 size of image for the device of 480*800 is not the right way to do. So, try to make several images in you backend like small(300*300), medium(500*500),large(1000*1000), original(XXX*XXX)
Afterwords, according to the device's current resolution fetch the appropriate image from url.
2. If your image are not having transparency try to use JPEG file instead of PNGs
Obviously, you will need to do some extra efforts but this would help you to minimize the 'OutOfMemory Exception'. However, after this as well there are many scenarios due to which you can face this exception.
I hope it would help you.
After profiling the an app, I found a memory leak using the above method. This worked for me using Glide and a viewpager:
Glide.with(context)
.load(imageURL))
.asBitmap()
.dontAnimate()
.diskCacheStrategy(DiskCacheStrategy.RESULT)
.into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
if (bitmap != null) {
imageView.setImageDrawable(bitmap);
}
}
});

FragmentStatePagerAdapter memory issue

I'm making an application with the ViewPager class and with a FragmentStatePagerAdapter adapter. I've read that the difference between the mentioned adapter and FragmentPagerAdapter is that the later stores all pages in memory at once, whereas FragmentStatePagerAdapter has only 3 loaded in memory at any given time.
So, here is the issue. I have a ViewPager with about 50 pages. There is a fragment on each page with a single ImageView image(and some other elements). After scrolling through around 20 unique pages, I usually get the Out Of Memory Error. So, my question is: How am I supposed to configure FragmentStatePagerAdapter to only have about 3 pages loaded in memory at any given time? This is the code for my adapter:
mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
#Override
public Fragment getItem(int position) {
Song song = mSongs.get(position);
return PlayFragment.newInstance(position);
}
#Override
public int getCount() {
return mSongs.size();
}
#Override
public void destroyItem(View collection, int position, Object o) {
View view = (View)o;
((ViewPager) collection).removeView(view);
view = null;
}
#Override
public Object instantiateItem(View context, int position) {
ImageView imageView = new ImageView(getApplicationContext());
imageView.findViewById(R.id.albumimage);
imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), position));
((ViewPager) context).addView(imageView);
return imageView;
}
});
The destroyItem and instantiateItem methods currently do nothing. I've added them after reading about this from someone else's question. There is no difference as of now if I have these two methods in my code or not.
I've read other questions similar to mine, but I have finally decided to ask a question after having attempted to solve the problem on my own with no good results.
I tried setting the ImageView to null in onDestroy(), but nothing happened.
Bitmap created by BitmapFactory.decodeResource(getResources(), position) has to be released manually by calling Bitmap.recycle()
https://developer.android.com/training/displaying-bitmaps/manage-memory.html
I've started using bitmaps as input for ImageView. The code below works fine.
albumimg = BitmapFactory.decodeFile(mSong.getImg());
mImg.setImageBitmap(albumimg);
mImg.setVisibility(View.VISIBLE);
And this in onDestroy() and onDestroyView():
if(albumimg != null) {`albumimg.recycle(); }`
Thanks for the help. :)

Android - How to stop images reloading in Recyclerview when scrolling

I was creating an app for loading images from internet in cards. I am using Recyclerview for listing purpose. For downloading images, I have tried Picasso, universal-image-downloader and other custom methods but everywhere I have this problem:
Whenever I scroll down and up the images in imageview are getting reloaded. I have even kept a condition to check if the imageview contains an image but that also doesn't seem to work or my code may be faulty. But I am not able to find a solution to this problem. Below I have attached the 'onBindViewHolder' function of my code which I think is the problematic part. Here images is a string arraylist containing image urls.
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.default_card, parent, false);
ViewHolder viewHolder;
viewHolder = new ViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
String url = images.get(position);
if (holder.imgView.getDrawable() != null) {
//Do Nothing
} else {
ImageLoader.getInstance().displayImage(url, holder.imgView);
}
}
You can use .fit() with Picasso. Hope that solves your problem
Picasso.with(persons.get(i).context)
.load(persons.get(i).photoId)
.placeholder(R.drawable.placeholder)
.fit()
.into(personPhoto);
At first, there is a bug in your code - you have to remove the condition from onBindViewHolder. All ImageView instances has to be updated each time they are about to display.
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
String url = images.get(position);
ImageLoader.getInstance().displayImage(url, holder.imgView);
}
The reloading part:
The problem is, that you are probably not using any cache (in-memory, or disk based). I would not elabarote on this a lot, caching of images in collection views is a really common problem and I'm sure a quick StackOverflow search will give you a lot of results.
Just in particular - Universal image does not have caching enabled by default You can turn it on this way.
DisplayImageOptions options = new DisplayImageOptions.Builder()
...
.cacheInMemory(true)
.cacheOnDisk(true)
...
.build();
ImageLoader.getInstance().displayImage(imageUrl, imageView, options); // Incoming options will be used
Or you can set it globally for your application, just check the docs.
You can use this to solve your issue.
mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 0);
If you don't want to use Cache, you can use setItemViewCacheSize(size) and mention the size where you don't want to reload. That's it. Recyclerview version 24.0.0

Problems using bitmaps for thumbnails

I'm trying to make an app that takes videos and then displays the videos in a gridview. I have gotten to the point where all the video thumbnails are showing up in the gridview like they should. However, my problem is that the method I am using has a lot of lag time. It takes some time to get the gridview loaded and when I try to scroll there is always some sort of lag. This is most likely due to the fact that I'm using bit maps for every video thumbnail.
I was wondering what I could do to make this a lot quicker and smoother. My code is posted below.
class VideoAdapter extends BaseAdapter {
Context context;
ArrayList<String> videopathlist;
Bitmap bmthumb;
VideoAdapter(Context c, ArrayList<String> filepathlist){
context = c;
videopathlist = new ArrayList<String>();
this.videopathlist.addAll(filepathlist);
Log.i(TAG, "" + videopathlist);
}
#Override
public int getCount() {
return videopathlist.size();
}
#Override
public Object getItem(int position) {
return videopathlist.get(position);
}
#Override
public long getItemId(int arg0) {
return 0;
}
#Override
public View getView(int posiiton, View arg1, ViewGroup arg2) {
ImageView imageview = new ImageView(context);
bmthumb = ThumbnailUtils.createVideoThumbnail(videopathlist.get(posiiton), MediaStore.Video.Thumbnails.MINI_KIND);
if (bmthumb != null) {
Log.i(TAG1, "There is a thumbnail");
imageview.setImageBitmap(bmthumb);
} else {
Log.i(TAG1, "There is NOT a thumbnail");
}
imageview.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageview.setLayoutParams(new GridView.LayoutParams(160, 160));
return imageview;
}
Thanks for your help
2 things:
Use a ViewHolder to cache everything, as described here: Making ListView Scrolling Smooth (applies to GridViews too)
Also, use an ImageLoader to load images dynamically. There are many available or you can build your own. Here are some:
Volley
Android-Universal-Image-Loader
ImageLoader
All these loaders make use of Bitmap caches as described here: Caching Bitmaps
Also, you could potentially be actually creating the thumbnail bitmaps when returning the view. This strikes me as very expensive. I would do this as a seperate step, unrelated to displaying them. For example, kick off an AsyncTask when your activity is first created to generate any new thumbnails. Then in your adapter just query the MediaStore to get the generated thumbnails.

Android ViewPager Memory leak

I need to create ViewPager in Android with 5 slides, each consists of image and text. I have an array with resources for images:
private static final int[] images = {R.drawable.tutorial_step_01, R.drawable.tutorial_step_02, R.drawable.tutorial_step_03, R.drawable.tutorial_step_04, R.drawable.tutorial_step_05, R.drawable.tutorial_step_06};
then I create adapter:
#Override
public Object instantiateItem(ViewGroup container, int position) {
LinearLayout tv = (LinearLayout) inflater.inflate(R.layout.tut_slide, null);
TextView title = (TextView) tv.findViewById(R.id.tut_title);
title.setText(getResources().getText(titles[position]));
TextView content = (TextView) tv.findViewById(R.id.tut_content);
ImageView image = (ImageView) tv.findViewById(R.id.tut_image);
slide_image = BitmapFactory.decodeResource(getResources(), images[position]);
image.setImageBitmap(slide_image);
content.setText(getResources().getText(contents[position]));
((ViewPager) container).addView(tv, 0);
return tv;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((LinearLayout) object);
//
}
trouble is that fact android don't want to collect image after I choose another page. So, after 10-15 changes it goes out with OutOfMemory exception. Then I added to initializung rows
if (slide_image!= null) {
slide_image.recycle();
System.gc();
}
And it's work good! But except one thing: I have black screen instead of first image, whcih is replaced by real one after few flips. So I don't know what to do with such memory leaking
Well, I solved the problem finally. I faced it with a very similar case and as I've seen so many questions related to the same problem, I chose this question as it's yet not answered.
The PagerAdapter should call the destroyItem method not only when it surpasses the offLimitScreenPageLimit but also when a screen rotation occurs, but it doesn't, so it has to be forced to do so... to achieve it, you just have to set to null the adapter on the onStop or onDestroy method of the activity.
#Override protected void onDestroy(){
pager.setAdapter(null);
}
Cheers!
It's not clear what you are using but I encountered a similar problem.
I'm assuming you are using FragmentPagerAdapter.
When you scroll away using that adapter, it does not destroy the pages out of view and out of cache. If there is an ImageView in a fragment used by FragmentPageAdapter, OOM is inevitable
Just change the extend of the adapter to
FragmentStatePagerAdapter
This will destroy the fragments not in use and leave more memory free for new fragments.
It's still not perfect, I have found that sometimes I can scroll faster than the garbage collector picks up the destroyed bitmaps, but its pretty damn close.
If I was looking to improve it, I would override destroyItem, and then get the bitmap in use from the imageview and .recycle the bitmap.
Recycle ImageView's Bitmap
That behaviour shouldn't be related with a memory leak. It looks like it's related to when and how you recycle bitmaps within your viewpager updating lifecycle. Try calling onPageSelected() or notifyDatasetChanged() manually at some point on your initialization.
This solutions might not solve the problem completely, but give it a try. It's hard to tell with your explanation.
In My case I have 31 page in ViewPager. I use this :
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
if(dbCon!=null)
dbCon.close();
ViewPager viewPager = (ViewPager)container;
View view = (View) object;
viewPager.removeView(view);
}
and everthing works fine. Alhamdulillah.

Categories

Resources