custom listview slow processing - android

I created a custom ListView and followed the below procedure provided by google
http://www.youtube.com/watch?v=wDBM6wVEO70
But the ListView scrolling is too slow. Any solutions?
Here is my code of getview
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
if(convertView==null){
convertView=inflater.inflate(R.layout.gridlevelinflate, null);
holder=new ViewHolder();
holder.levelImg=(ImageView)convertView.findViewById(R.id.lvlPic);
holder.levelName=(TextView)convertView.findViewById(R.id.lvlName);
holder.levelLeft=(TextView)convertView.findViewById(R.id.lvlLeft);
convertView.setTag(holder);
} else {
holder=(ViewHolder)convertView.getTag();
}
holder.levelImg.setImageResource(R.drawable.ruzzle);
holder.levelName.setText("Level " + position++);
return convertView;
}

There's nothing in this code that stands out as a significant inefficiency that would make your ListView slow. The problem is probably somewhere else.
That said, here are the things that are less efficient than they could be:
Since it's constant, you could move the setImageResource call into the initial holder setup (when convertView == null), but I wouldn't expect that to be terribly slow unless R.drawable.ruzzle is extremely complex (and perhaps not even then).
You're finding R.id.lvlLeft and then not using it, so that could be removed, but it's minor as it only happens when views aren't recycled.
You could use position+1 instead of position++, but that's incredibly minor and won't make any perceptible difference.

Related

How to restrict the execution of some code for a particular item in getView?

I have a Async call happening from the getView() in a custom listview adaper, as follows(Last Line):
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
holder=new Holder();
View rowView;
rowView = inflater.inflate(R.layout.list_posts_item, null);
holder.tvTitle=(TextView) rowView.findViewById(R.id.tvTitleNamePost);
holder.ivPrimaryImage=(ImageView) rowView.findViewById(R.id.ivPrimaryImage);
holder.ivPrimaryImage.setId(position);
holder.ivPrimaryImage.setTag(listOfPosts.get(position).getPostId());
holder.tvLocality=(TextView) rowView.findViewById(R.id.tvLocalityPosts);
holder.tvDateCreated=(TextView) rowView.findViewById(R.id.tvDateCreated);
holder.tvTitle.setText(listOfPosts.get(position).getTitle());
holder.ivPrimaryImage.setImageBitmap(listOfPosts.get(position).getImage());
holder.tvLocality.setText(listOfPosts.get(position).getLocality());
holder.tvDateCreated.setText(listOfPosts.get(position).getCreatedDate());
postId = listOfPosts.get(position).getPostId();
Image image = new Image();
image.setImg(holder.ivPrimaryImage);
if(!"N".equalsIgnoreCase(listOfPosts.get(position).getHasImage()))
new GetPrimaryImages().execute(image);
return rowView;
}
The problem is, the Async call is happening everytime the getView is getting executed. Can we restrict the call, only when the item in consideration is an imageView?
You shouldn't really be calling async from getView(), as its best to keep this as light as possible and also try to call it as little as possible by proper use of viewholders and recycler views. Im not 100% what way youve done the rest of your code, so here is a good run through of all the details. There is dozens of other in depth explanations online that will go through all the stuff you need to understand regarding getView() performance.
This helpled:
android:layout_height="wrap_content"
in my ListView definition. Changing it to fill_parent/match_parent would avoid it.

Holder pattern and convert view

I have learned earlier about great approach to increase performance - Holder Pattern. This is good idea to speed up UI and animation.
It is clearly why and how to use it.
I have used it a lot , but now I am little bit confused about this.
When getView method is called it has three arguments one is converView. As I undertand it is previously inflated view of list item, so the are some questions about this.
If it is previously inflated view, why not just to use it, return it from method, of course check to null before.
How does this implemented,listview class has private array or another data structure that holds all inflated views ?
Why this feature is not implemented in adapters ?
Thanks in advance.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder =(ViewHolder) convertView.getTag();
}
If you would just use the convertView, you would need to get hold of your views with findViewById(). This is exactly what the ViewHolder pattern is trying to avoid. findViewById() is a surprisingly expensive method and can slow down your app, especially if you constantly call it when scrolling through lists.
Listviews reuse the layouts of the child items, to avoid having to inflate the same views over and over again.
Most adapters were already available to developers before people came up with the ViewHolder pattern. The latest new list view, RecyclerView, has an adapter that enforces the use of the ViewHolder pattern.
You can't call ViewHolder holder =(ViewHolder) convertView.getTag(); at the first line of getView. Because convertView could be null. Try again like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHoldler holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(ctx).inflate(
R.layout.frag_home_gridview_item, null, false);
holder = new ViewHoldler();
holder.iv = (ImageView) convertView
.findViewById(R.id.gridview_item_label);
holder.tv = (TextView) convertView
.findViewById(R.id.gridview_item_name);
convertView.setTag(holder);
} else {
holder = (ViewHoldler) convertView.getTag();
}
holder.tv.setText(getItem(position));
holder.iv.setImageResource(this.ids[position]);
return convertView;
}
private class ViewHoldler {
ImageView iv;
TextView tv;
}
Because you most likely are not using same views. Say you have a row which has a TextView in it. The convertview is one of the recycled views and it's textview may be displaying different information that it should be.
It does keep as many views as there are visible, once you scroll down the top view get's recycled and you come back to answer 1.
I don't understand, your code is from the BaseAdapter class.

Listview lags; issue with cache file?

I have experienced quite a bit of lag recently when using ListView in one of my applications. About the same time the problem manifested itself, I started getting the following error message upon shutting down the application.
E/libEGL(8501): error creating cache file /data/data/[my application's namespace]/cache/com.android.opengl.shaders_cache: No such file or directory (2)
As my device is not rooted, I do not have access to the location and could verify the file exists, is corrupt, or has been deleted. I am not sure what the problem is. First of all, could the problems be related? It seems like a strange coincidence. To be clear; I do use a view holder pattern to optimise my ListView and it has worked just fine before so my implementation is an unlikely culprit.
Update As requested, here the code for my Adapter's getView method. While I don't believe it has anything to do with the problem, it worked fine before for about 5 months just fine, I am happy to cater to your questions.
#Override
public View getView(int position, View convertView, ViewGroup parentViewGroup)
{
final PersonaViewHolder viewHolder;
final Persona persona = provider[position];
if (convertView == null)
{
convertView = LayoutInflater.from(parentViewGroup.getContext()).inflate(R.layout.list_personas, null);
viewHolder = new PersonaViewHolder(
(ImageView) convertView.findViewById(R.id.persona_icon),
(TextView) convertView.findViewById(R.id.persona_description));
convertView.setTag(viewHolder);
} else {
viewHolder = (PersonaViewHolder) convertView.getTag();
}
Bitmap icon = BitmapFactory.decodeResource(parent.getResources(), persona.getPicture());
viewHolder.setIcon(icon, 50, 50);
viewHolder.setDescription(persona.getName() + " is a " + persona.getType() + "!");
return convertView;
}
As your probably are aware, having any type of actual processing in your getView() method should be avoided. While I can't speak to the cache file issue, I do notice you have the line:
Bitmap icon = BitmapFactory.decodeResource(parent.getResources(), persona.getPicture());
In your getView() method. You should consider implementing a LruCache to cache your bitmaps. Documentation on that can be found here:
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
Additionally, on the line
convertView = LayoutInflater.from(parentViewGroup.getContext()).inflate(R.layout.list_personas, null);
You should not use .inflate(R.layout.list_personas, null); but instead use .inflate(R.layout.list_personas, parent, false);
Note the difference in the last arguments. This is the proper way to attach your view to it's parent. It may not improve performance, but it's correct.

HorizontalListView performance issues : Calls inflates for every subview

I'm making a Pulse kind of UI for my app. For this I'm using HorizontalListView class as given here. However, this class has performance issues and delivers a noticeable lag.
To confirm this I assessed it using TraceView Profiler and found that this class doesn't reuse views altogether and calls inflate() method for every call inside getView().
Here is how I'm designing the adapter:
public View getView(final int position, View convertView, ViewGroup parent) {
final BaseAssets baseAsset = baseAssetsList.get(position);
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.gallery_list_item, parent, false);
viewHolder.newLabel = (ImageView) convertView.findViewById(R.id.iv_new);
viewHolder.assetImage = (ImageView) convertView.findViewById(R.id.iv_thumbnail);
convertView.setTag(viewHolder);
} else
viewHolder = (ViewHolder) convertView.getTag();
}
class ViewHolder {
ImageView newLabel;
ImageView assetImage;
}
Am I doing anything wrong? If not, please suggest me workarounds to improve performance. Possibly some other library you would have tried or any way to reuse views in my current library. Thanks !
Seems like you're never calling convertView.setTag(viewHolder); in the case the convertView is null. You cant retrieve the ViewHolder without first setting it as the tag.
EDIT:
Other than that your code seems fine, my best guess would be that the problem lies in the implementation of the HorizontalListView.
I looked at its source (HorizontalListView.java) and if you can experiment with the source, try checking if mRemovedViewQueue is empty before it makes any calls to mAdapter.getView.If it is, then its not handling the recycling properly.

Issue when fast scroll a listview

I am working on a small project where I create a listview bound to an ArrayAdapter. In the getView() function of ArrayAdapter, I do a loading of images from web urls on thread, and set them to list items (based on position of course, so url[0] image is set to list_item[0] etc). It all seems to work well.
However when I was testing the app, I noticed that if I wait my listview to fully display, then perform a fast scroll back and forth, I see sometimes the image on one list item is misplaced on other (like being in an intermediate state). However it's not going away until I scroll the wrongly-displayed-item out of screen and then back.
I do not know if it relates to my loading web url using thread, or maybe loading image from local resource folder can have the same issue.
This actually leads to a question I have about getView() function. I think the logic is correct in my getView() because it's as simple as a binding of url to view based on position. And whenever getView() get a chance to be called, like when I scroll an item out of screen then back, it will make the list item display correctly.
The thing I do not understand is how to explain the issue that happened (like an intermediate state), and how to avoid it when writing code?
I paste my adapter code piece below, but I think the question maybe a general one:
#Override
public View getView(int position, View v, ViewGroup parent) {
ViewHolder viewHolder = null;
if (v == null) {
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(layoutResourceId, parent, false);
viewHolder = new ViewHolder();
viewHolder.title = (TextView) v.findViewById(R.id.title);
viewHolder.description = (TextView) v.findViewById(R.id.description);
viewHolder.image = (ImageView) v.findViewById(R.id.image);
v.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) v.getTag();
}
listItem item = items[position]; //items is some global array
//passed in to ArrayAdapter constructor
if (item != null) {
viewHolder.title.setText(item.title);
viewHolder.description.setText(item.description);
if (!(item.imageHref).equalsIgnoreCase("null")) {
mDrawableManager.fetchDrawableOnThread(item.imageHref, viewHolder.image);
} else {
viewHolder.image.setImageDrawable(null);
}
}
return v;
}
}
static class ViewHolder {
TextView title;
TextView description;
ImageView image;
}
I have same issue when scroll quickly it alternate the vales of some item to others, just like background color of some items if changes randomly. I solved this issue by searching a lot and find exact solution is just adding these two methods in your adapter if you are using ViewHolder in your adapter
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
Assuming that you are not caching the downloaded image.. lets see the following code:
if (!(item.imageHref).equalsIgnoreCase("null")) {
mDrawableManager.fetchDrawableOnThread(item.imageHref, viewHolder.image);
} else {
viewHolder.image.setImageDrawable(null);
}
Now if the image view is getting reused then it would already have the old image for the assigned list item. So until the thread download the image from the network it would display the old image and when the thread download the image for the current item it would be replaced with the new image. Try to change it to:
if (!(item.imageHref).equalsIgnoreCase("null")) {
viewHolder.image.setImageDrawable(SOME_DEFULAT_IMAGE);
mDrawableManager.fetchDrawableOnThread(item.imageHref, viewHolder.image);
} else {
viewHolder.image.setImageDrawable(null);
}
Or you can use something link smart image view that supports HTTP URI and also caches the images. Check out following link for smart image view:
https://github.com/loopj/android-smart-image-view
http://loopj.com/android-smart-image-view/
Add ImageLoader class from below link in your project.
link
just call DisplayImage() methode of Image loader class as below in getView()
ImageLoader imageLoader = new ImageLoader();
yourImageView.setTag(URL_FOR_Your_Image);
imageLoader.DisplayImage(URL_FOR_Your_Image,ACTIVITY.this, yourImageView);
Your images will load in background as you want without wait.
I think you should declare your downloader method fetchDrawableOnThread() as "synchronized" . Because a lot of threads are working simultaneously and any thread which started later, can end earlier. So there are chances of images being misplaced.
It happened to me for a long time. Finally "synchronized" helped me do it cleanly. I hope it helps you too.
I give it a try with synchronization again. Either synchronize the fetchDrawableOnThread(), or synchronize the global hashmap data within fetchDrawableOnThread(). First i thought the issue is gone, but when i tried more times later, i found the issue is still there.
Then i thought about the synchronization. fetchDrawableOnThread() is called from getView(), and getview() itself does not have a concurrency issue. Even if as Yogesh said, what happened INSIDE getView() is thread-based, and return early or late, it can not affect the correctness of getView(), i.e. the list item's display, only the sooner or later.
What i did(synchronization) inside fetchDrawableOnThread() i think it's still correct, 'cause i used a hashmap to cache images downloaded from remote url, and this hashmap is read/write upon in a multi-thread situation, so it should be locked. But i do not think it's the rootcause of the UI misplace, if hashmap is messed up, the image misplacement will be permanent.
Then i looked further on convertView reuse mechanism based on Praful's explanation. He explained clearly what happened when image always comes from remote and no cache locally, but my situation is i waited my list to display fully, i.e. all images download complete and cached complete, before i do the fast scroll. So in my experiment, the images are read from cache.
Then when inspecting my code, i saw one minor difference in the use of convertView as in getView() method, a lot of the example usages are like this:
public View getView(int position, View convertView, ViewGroup parent) { // case 1
View v = convertView;
.... // modify v
return v;
}
However the example i happened to copy from use:
public View getView(int position, View convertView, ViewGroup parent) { // case 2
.... // modify convertView
return convertView;
}
I thought it makes no difference at first, 'cause according to what android says, 'ListView sends the Adapter an old view that it's not used any more in the convertView param.', so why not use 'convertView' para directly?
But i guess i was wrong. I changed my getView() code to case 1. Boom! everything works. No funny business ever no matter how fast i scroll the list.
Quite strange, is convertView only old, or is it old & in-use? If the later, we should only get a copy and then modify..... ??

Categories

Resources