I have a very frustrating problem and I don't have an idea what is wrong.
I build simple XML which contains only GridView. This gridview should show images (ImageView) downloaded from specific urls which I retreive with my code.
So the idea of this APP is only to download images from URLS and show those images in GridView. First I must say that I'm using Universal Image Loader library. First please look at my getView code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView iv;
if(convertView == null) {
iv = new ImageView(MyActivity.this);
iv.setLayoutParams(new GridView.LayoutParams(size,size));
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
Log.d("APP", "convertView is NULL");
}
else {
iv = (ImageView) convertView;
Log.d("APP", "convertView is NOT null");
}
loader.displayImage(URLS.get(position), iv);
return iv;
}
PROBLEM:
Code above works, and it shows images in GridView. But when I scroll down, I see that:
All images were downloaded (Even those images who are not visible )
When i scroll back up I see that image which was in place 0, is in place 1 and it has been downloaded again. When I scroll back down, images switched positions again. Scrolling back up, I see again images are switching places again ( and downloading again). This goes infinite.
HOW I SOLVED THIS PROBLEM:
I solved this problem by NOT checking if convertView variable is null. Actually I ommited if/else clause.
I used cache in memory = true. So downloading stops and images are in place for ever.
So why I'm asking for help? Mainly because i know that i should not ommit if/else clause for performance reasons. I don't want to mess with users memory and I would like to bring them fast and reliable user experience. Also i would like to know what could be wrong.
Thank you for help. Much appriciated.
Loader needed some time to load picure in ImageView. Because you reuse view for different images you can see a previous image in the view while new image is loading. You can set resetViewBeforeLoading(true) in DisplayImageOptions for avoid this effect.
Also you can use disk cache to avoid downloading images every time from the network. Also limit size of memory cache and set other settings, but I think memory cache is useful, it improves user experience.
And don't forget to use setOnScrollListener(new PauseOnScrollListener(loader, true, true)) to avoid lags on scrolling.
GridView recycles list items for performance purposes. So when you are scrolling, the list items are getting recycled in different places and then your code is re-populating them. Sometimes this lags for images.
I'd recommend using a library that handles this sort of thing like Picasso.
I was having the exactly same problem as you, and didn't want to disable convertView checking either.
My solution was to increase the memory cache size in ImageLoaderConfiguration.
Before, i used to use it like this:
.memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024))
So I changed it to:
.memoryCache(new UsingFreqLimitedMemoryCache(10 * 1024 * 1024))
I don't know if 10*1024*1024 is too much or it will cause any problems, but it seems to have fixed the problem to me and i haven't had any problems until now.
Related
I have an android app with different graphical elements (ImageViews). Some ImageViews can be changed by pressing them, and then they will show another graphic. I have two systems for this, and my problem is the same for both.
In the first case I have layered two ImageViews on top of each other, and then I call each of them and set their visibility programmatically. In the second case I use the function "setImageResource" and change between different drawables.
In both cases I have the problem that the app quite often seems to "hickup" and for a split second show the wrong 'other' graphical element for each element that has a graphical alternative. It shows the wrong graphic for a split second and then jumps back to the correct graphic.
I can not find anything about this online, anyone here that recognizes this issue or have any clues as to what the problem might be?
relevant code:
public void setLight1(int bo) // changes a graphical element by drawable
{
ImageView vw = (ImageView) findViewById(R.id.light_1);
if (bo > 0)
vw.setImageResource(R.drawable.lampaljus);
else
vw.setImageResource(R.drawable.lampaslockn);
}
private void setStickMid(int stick) // changing between two layered imageViews
{
switch (stick)
{
case 1:
ImageView vw_1 = (ImageView) findViewById(R.id.stick_1_up);
vw_1.setVisibility(View.INVISIBLE);
ImageView vw_2 = (ImageView) findViewById(R.id.stick_1);
vw_2.setVisibility(View.VISIBLE);
break;
// the rest of the switch statement omitted, just more of the same
Of the top of my head, try these:
Move all the findViewById code to onCreate of your activity, to avoid calling it multiple times.
How big are the assets R.drawable.lampaljus, R.drawable.lampaslockn and others, dimensions wise. Because trying to load high resolution drawables, especially PNGs, will take a lot of memory.
But I can provide a better solution, if you can post the whole xml and Activity/Fragment code.
I have successfully implemented lazy loading of list images and list items in Android listview. I am using Android 4.0+ and Java 7.
The algorithm i followed is:
List data(including image URL) is downloaded from internet as and when user scrolls the list.
When scroll state is idle, list images are loaded.
In the background thread, images are first checked for in the cache. If not present in cache, they are downloaded and stored into the cache.
Lastly image is set to imageview in the listview and adapter is notified.
The only problem is I am not clear about when to recycle bitmaps. I tried using bitmap.recyle() at many places but I got the following error:
java.lang.IllegalArgumentException: Cannot draw recycled bitmap
It is not possible to add that vast code over here. Also there are some privacy issues. Can someone please help me about this?
EDIT
My application size increases from 727 KB (at the time of installation) to 14 MB.
After I recycle my bitmaps, in getView() of adapter I get "cannot generate texture from bitmap ".
Can anyone suggest how to get rid of it?
Recycling a bitmap renders it unusable. Only recycle when you're completely done with it. In your case, that means after it's been evicted from the cache. You'll also want to make sure that none of your existing views reference it.
As of ICS the need to recycle isn't necessary. There are a few instance where you would want to but considering most listview implementations it probably won't be necessary.
You can check out this video by Chet Hasse for more info on reusing bitmaps which would be better if they are the same size. DevBytes: Bitmap Allocation
Bitmap recycling should be performed differently in different versions of Android. It is best to implement in a way that covers the majority of versions.
As others here said, recycle() renders your bitmap unusable, recycle() is meant to be used once you are finished with the bitmap and would like to induce a garbage collection. I think you should use it on your activity onPause()/onStop().
See here for more information:
Managing Bitmap Memory
I've read a ton of posts about both these errors and can't seem to find a solution that works for me.
I have a basic listview activity of animals. When you click on an item in the list, it opens the AnimalViewActivity and displays a simple png image inside an ImageView.
Here's where I set the image for the ImageView:
public void getImage() {
String imageName = myService.yourAnimals.get(key).getType();
System.out.println(imageName);
Resources res = getResources();
int resourceId = res.getIdentifier(imageName, "drawable", getPackageName() );
Drawable drawable = res.getDrawable( resourceId );
image.setImageDrawable( drawable );
}
Then when I leave AnimalViewActivity to return to the listview activity, I do this:
#Override
protected void onDestroy() {
super.onDestroy();
//((BitmapDrawable)image.getDrawable()).getBitmap().recycle();
image.getDrawable().setCallback(null);
//System.gc();
System.out.println("Destroy image!");
}
If I uncomment the recycle() line, I get the "cannot draw recycled bitmaps" error.
If I leave it how it is, I get the outofmemoryerror for bitmapfactory.
java.lang.IllegalArgumentException: Cannot draw recycled bitmap
OR
java.lang.OutOfMemoryError
android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
Either one of these errors occur after I've opened the ViewAnimalActivity about 20 times and the app crashes.
The only thing that seems to work forever is System.gc() , but I know that is not a good solution.
I'm testing it in android4.1, but the minSDK is set to android3.0
I read that the javaVM error for bitmap recycling was fixed in 3.0?
For some reason, garbage collection is not happening fast enough without explicitly calling the gc. These images are small, mostly between 100-300kB.
Any ideas?
EDIT:
It seems happier with
image.setImageDrawable( null );
I think I tried this before when I still had it set on Android2.2, but Android3.0 is happy with it so far.
The documentation for recycle() states explicitly that it is for "advanced use". It's more important to set the references to null, actually.
This is an advanced call, and normally need not be called, since the
normal GC process will free up this memory when there are no more
references to this bitmap.
There is, in addition, the possibility to look if a Bitmap has been recycled before drawing it. (post your logcat to see where you're having the error. You could add code to verify if the Bitmap has been recycled and re-load it).
Overall, I think you'd best look at the Android Training Lessons that tell you how to display Bitmaps efficiently.
Why don't you try setting the drawable to null first, then recycle it? The problem is probably because the drawable is still on the image when you recycled it, thus causing the "cannot draw recycled bitmaps error". If you set that to null first, thereby setting the actual ImageView (or whatever it is that you're using) to null, then recycling it, that will probably solve your issue.
Edit: WOOPS! I totally forgot if you set a drawable to null, then you can't retrieve the bitmap you're trying to recycle. Try assigning that to a variable first, then recycle it.
In the getView() method of my GridView, I am doing the following:
public View getView(int position, View convertView, ViewGroup parent) {
convertView = this.inflater.inflate(..);
ImageView image = (ImageView) convertView
.findViewById(R.id.imageButton1);
imageLoader.displayImage("URL/"
+ sampleArray.get(position), image);
sampleArray here is an array I've loaded when the Adapter is created. It is used when lazy-loading (with this tool: https://github.com/nostra13/Android-Universal-Image-Loader) these images and basically is a part of the image URL that determines the picture to load. Now, as far as I know, best practice is to do only inflate the view if it's null:
if(convertView == null){
this.inflater.inflate(...);
When I do this, it is infact faster, but the image loading is weird. If I scroll down and scroll back up, the images switch within the row, meaning in the top row, the 1st column image might randomly switch with the second column image. The array I am using (sampleArray) doesn't change. I know it has something to do with position and getView() being called, but I'm not sure why this behavior happens.
If I do it the way I am doing now (the first block of code), meaning I inflate it every time, it works perfectly but loads slowly. Why does this behavior work like this?
Thanks in advance.
EDIT:
Here's what I think is happening. GetView() is being called multiple times, which is somehow not in sync with the "position" argument, causing the wrong element to be picked out of the array. I'm still puzzled by it.
You should use
.resetViewBeforeLoading()
and probably
.showStubImage(R.drawable.empty_image)
on your DisplayImageOptions when you initialize the UIL.
This will reset the view before the loading.
This is needed because you are reusing the views on gridview.
Have you tried to clean up the original image:
image.setImageBitmap(null);
I check the source code of the image loader, one of the methods is like:
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options)
I think you can also try to set the option as "resetViewBeforeLoading = true"
the reason I think that the weird thing happens is, the view is recycled by the gridview, which is still with the original bitmap when showing up, before the loader attach it with the new bitmap.
if it still doesn't work, please lemme know.
EDIT
I did more investigation, the reason I think is from the recycling.
Let me explain more clearly:
reason:
suppose we have total 12 cells in our data
on screen, there are only 9 views visible
at very beginning, the gridview will create and show 9 on screen:
view_1_1 view_1_2 view_1_3
view_2_1 view_2_2 view_2_3
view_3_1 view_3_2 view_3_3
after scrolling(scrolling up, to show the row 4), at the moment, grid view will put view_1_1, view_1_2, view_1_3 in recycle bin.
and then will get 3 views for row 4 from recycle-bin, which are put in the getView as "convertView"
so, the view_1_1, view_1_2, view_1_3 will be reused.
suppose at the beginning, the views are attached with bitmap
view_1_1 : bitmap_1_1
view_1_2 : bitmap_1_2
view_1_3 : bitmap_1_3
when these view are recycled and show at the row 4, since the bitmaps are loaded asynchronously, the bitmap_1_1, bitmap_1_2, bitmap_1_3 will show up on the row 4, and laterly be replaced by those new bitmap.
how to confirm it
1.one way for debugging grid view I like is put some "id" on the image, the simple way is just put "ImageView" toString()
put an overlay view above each imageView, and write id in it, like:
String s = viewInfoHolder.imageView.toString();
viewInfoHolder.overlay.setText(s.substring(s.length() -3));//last 3 chars are enough and cleanup
2.for making the replacement slow, modify the
com.nostra13.universalimageloader.core.LoadAndDisplayImageTask
the last row in method:
public void run()
from:
handler.post(displayBitmapTask);
to:
handler.postDelayed(displayBitmapTask, 2000);
and you could find out exactly how the grid view works.
Hope it helpful :)
I am working on a app that shows some places in a listview. In every listview item there is a arrow pointing towards the place(hotel, bar etc).
The problem is I don't know how to keep this arrow updated efficiently, without cashing views locally( which, according to a Google I/O video, is something i should never ever do).
The arrow needs to be updated on every phone orientation sensor event, which is many times a second.
So is there a better approach than calling notifyDataSetChanged() on every event and refiling every list item data?
UPDATE (for #dharan and anyone interested):
I have stopped working on this project because I have a full-time job now, but I thought of a solution (unimplemented/untested).
First limit the angle of rotation to a fixed step, (eg: 5, 10, 15, 20, ... 355, 360) then cache the rotated image for each angle of rotation in order to avoid expensive image rotation calculations (but at a higher memory cost).
After that I would make my getView() method know when to only update the image instead of all the data. In my case this is as easy as:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView != null && place[position].id == (Integer)convertView.getTag()){
//the place information is already set correctly so just update the image
}
...
}
After these modifications I believe that calling notifyDataSetChanged() should not cause serious performance issues.
If you don't have too many items in the ListView you could convert to using a plain LinearLayout and control those items individually. The problem with LinearLayout though is if an item changes its size then everything (ie all children) has to be relayed out. So triggering a change in one item can re-trigger other things to layout as well. Now because you're changing a compass needle you might be able to skirt around it because that shouldn't cause each row to change its overall size. You just need to repaint that item.
The other option is to write your own layout that takes some short cuts making some assumptions the general purpose layout managers can't. The last thing I might look at is what does notifyDataSetChanged() does under the covers. You might be able to extend ListView and write a method that only redraws the row that changed (of course this assumes the height of the row hasn't changed). If the row height changes then everything after that row has to be relayed out.