Appropriate use BaseAdapter class specifically getView method - android

So far I have seen several examples of applications that use BaseAdapter and ArrayAdapter<?>. but I am still not completely clear the reasons why should be that way.
The first example is extending from ArrayAdapter<?>, this example is used in ListView, the following is the getView method
#override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
Holder holder = null;
// Holder represents the elements of the view to use
// Here are initialized
if(null == row) {
row = LayoutInflater.from(mContext).inflate(LAYOUT_ITEM_ID, parent, false);
holder = new Holder();
holder.titleTextView = (TextView)row.findViewById(android.R.id.title);
row.setTag(holder);
} else {
holder = (Holder) row.getTag();
}
// here do operations in holder variable example
holder.titleTextView.setText("Title " + position);
return row;
}
public static class Holder {
TextView titleTextView;
}
now in a second example a found is used BaseAdapter on a GridView this is the getView method
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mThumbIds[position]);
return imageView;
}
My question is: for a proper use of an Adapter is necessary to use a "Holder" static class, what implications does this have on application performance and compatibility on multiple devices (min API 8).

It's not necessary to use a holder class; it's more important to make sure that you re-use convertView whenever possible as this has a noticeable speed improvement. That being said, using a holder does offer even better performance, especially if you are displaying a lot of items, as getView won't have to inflate the xml every time.
This video explains this in greater detail: http://www.youtube.com/watch?v=wDBM6wVEO70

First, I'd like to point you to the World of ListView session that was held during Google I/O '10. It'll be worth watching that presentation (or read through the pdf) to better understand the ListView mechanics and why a 'ViewHolder/RowWrapper' pattern can significantly speed things up.
There are basically two key ingredients to optimizing the getView() logic:
Make use of recycled views (the convertView parameters that gets passed into getView())
Minimize the number of (expensive) findViewById() calls
In your first example, the ViewHolder pattern is applied to only inflate the row view if no recycled view is available - that's good. Secondly, it reduces the number of view lookups by tagging the row with a ViewHolder object, which acts as a wrapper for the TextView that was already retrieved earlier.
Your second example does not inflate a row view, but rather instantiates it at runtime. It still checks whether a recycled view is available and uses that if possible - again, that's good. Also, since the row view (or grid view in this case) is just a single ImageView, the convertView can simple be cast to an ImageView. Note that if the row/grid view would've consisted of more than just a single view, the ViewHolder approach from the first snippet would be the appropriate way to go.
That being said, since the row view in the first snippet is also just a single TextView, it could potentially be simplified using the same casting-convertView-approach as in the second snippet. Quite often row views will consist of multiple views though, so I'd suggest to always use the ViewHolder pattern, as that will give you most flexibility to accommodate future changes.

Related

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.

Async image loading, check if an image is recycled

This question came to me after reading this: Performance tips (specifically the part named "Async loading"). Basically he's trying to save info about a row to see if it's been recycled yet and only set the downloaded image if the row is still visible. This is how he saves the position:
holder.position = position;
new ThumbnailTask(position, holder)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
Where ThumbnailTask constructor is:
public ThumbnailTask(int position, ViewHolder holder) {
mPosition = position;
mHolder = holder;
}
In onPostExecute() he then does the before mentioned check:
if (mHolder.position == mPosition) {
mHolder.thumbnail.setImageBitmap(bitmap);
}
I just don't see how this gives any result. The Holder and the position are set in the constructor at the same time, to the same value (the position in the holder is the same as the position in mPosition). They don't get changed during the AsyncTask (it's true that the position might change in getView(), but the ones stored in the AsyncTask as private members are never manipulated). What am I missing here?
Also saving the position doesn't seem like a good option in the first place: I believe that it's not guaranteed to be unique, and if I recall correctly it resets itself to 0 after scrolling. Am I thinking in the right direction?
Background (you probably know this, but just in case): An adapter contains a collection of objects and uses info from these objects to populate Views (each view is a line item in the list). The list view is in charge of displaying those views. For performance reasons the ListView will recycle views that are no longer visible because they scrolled off the top or the bottom of the list. Here's how it does it:
When the ListView needs a new view to display it calls the Adapter's getView with an integer argument "position" to indicate which object in the Adapter's collection it wants to see (position is just a number from 1 to N -1) where N is the count of objects in the adapter.
If it has any views that are no longer visible, it will pass one of them in to the Adapter, too, as "convertView" This says "reuse this old view rather than creating a new one". A big performance win.
The code in the article attaches a ViewHolder object to each view it creates that, among other things, contains the position of the object requested by the ListView. In the article's code, this position is stashed away inside the ViewHolder along with a pointer to the field within the view that will contain the image. The ViewHolder is attached to the View as a tag (a separate topic).
If the view gets recycled to hold a different object (at a different position) then ListView will call Adapter.getView(newPosition, oldView...) The code in the article will store new position into the ViewHolder attached to the oldView. {make sense so far?) and start loading this new image to put into the view.
Now in the article, it is starting an AsyncTask to retrieve data that should go into the view) This task has the position (from the getView call) and the holder (from the oldView). The position tells it what data was requested. The holder tells it what data should currently be diplayed in this view and where to put it once it shows up.
If the view gets recycled again while the AsyncTask is still running, the position in the holder will have been changed so these numbers won't match and the AsyncTask knows it's data is no longer needed.
Does this make it clearer?
When AsyncTask is passed with ViewHolder and position it is given value of position (say 5) and value of reference (not a copy) to ViewHolder object. He also puts current position in ViewHolder (said 5), but the whole "trick" here is that for recycled views, the old ViewHolder object is also re-used (in linked article):
} else {
holder = convertView.getTag();
}
so whatever code references that particular ViewHolder object, will in fact check against its position member value at the moment of doing check, not at the moment of object creation. So the onPostExecute check makes sense, because position value passed to task constructor remains unchanged (in our case it has value of 5) as it is primitive, but ViewHolder object can change its properties, if view will be reused before we reach onPostExecute.
Please note we do NOT copy ViewHolder object in the constructor of the task, even it it looks so. It's not how Java works :) See this article for clarification.
Also saving the position doesn't seem like a good option: I believe
that it's not guaranteed to be unique, and it resets itself to zero
after scrolling. Is this true?
No. Position here means index in *source data set, not visible on the screen. So if you got 10 items to display, but your screen fits only 3 at the time, your position will be in range 0-9 and visibility of the rows does not matter.
As I understand you are trying to cancel the async-loading-task of the image when the view recycled, and no longer on screen.
To achieve that you can set up an RecyclerListener to the listview. It will be invoked when the listview don't need this view (when is not on screen), just before it passes it as a recycled view to the Adapter.
within this listener you can cancel your download task:
theListView.setRecyclerListener(new RecyclerListener() {
#Override
public void onMovedToScrapHeap(View view) {
for( ThumbnailTask task : listOfAllTasks )
task.viewRecycled(task);
}
});
and within ThumbnailTask :
public void viewRecycled(View v){
if(mHolder.theWholeView == v)
v.cancel();
}
Don't for to implement the cancel.
Note that its not the best approach since you should keep track of all your asynctask tasks. note that you could also cancel the task within the adapter where you also get the
public View getDropDownView (int position, View recycledView, ViewGroup parent){
//.. your logic
}
but note that this might require you to allocate the ThumbnailTask within the adapter with is not good practice.
note that you could also use image loading libraries that do eveything for you, from async download to chaching. for instance : https://github.com/nostra13/Android-Universal-Image-Loader
The accepted answer and Marcin's post already describe perfectly what's supposed to happen. However, the linked webpage does not and the google site on this topic is also very vague and only a reference for people who already know about the "trick". So here's the missing part, for future references, which shows the necessary additions to getView().
// The adapter's getView method
public View getView(int position, View convertView, ViewGroup parent) {
// Define View that is going to be returned by Adapter
View newViewItem;
ViewHolder holder;
// Recycle View if possible
if (convertView == null) {
// No view recycled, create a new one
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
newViewItem = inflater.inflate(R.layout.image_grid_view_item, null);
// Attach a new viewholder
holder = new ViewHolder();
holder.thumbnail = (ImageView) newViewItem.findViewById(R.id.imageGridViewItemThumbnail);
holder.position = position;
newViewItem.setTag(holder);
} else {
// Modify "recycled" viewHolder
holder = (ViewHolder) convertView.getTag();
holder.thumbnail = (ImageView) convertView.findViewById(R.id.imageGridViewItemThumbnail);
holder.position = position;
// Re-use convertView
newViewItem = convertView;
}
// Execute AsyncTask for image operation (load, decode, whatever)
new LoadThumbnailTask(position, holder).execute();
// Return the ImageView
return newViewItem;
}
// ViewHolder class, can be implemented inside adapter class
static class ViewHolder {
ImageView thumbnail;
int position;
}

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.

ListView get slow when the Base adapter.getview() method is called

I have been debugging my application and i saw that when i was scrolling the listview the method getView() of the class BaseAdapter is called to generate new views
public View getView(int position, View convertView, ViewGroup parent) {
Article article = this.articles.get(position);
return new MainView(this.context, articulo.getTitle() , articles.getDescription(),articles.getImgUrl());) }
when i scroll the listActivity to see the new items this method is invoked again to create the below list view items, as a consequence that the list items have images the ListActivity get slow, is there any way to create all the items view once, and not create ListItems when we are scrolling the listActivity
ListViews are highly optimized for performance, you should use ViewHolder inside your ListAdapter to cache the ListItems.
check http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html
the rule is, first set up your customview, pack everything inside your holder and pin this holder onto the view, the second time the view is used android simple extract the holder information (really fast).
It's probably slowing down because of the number of objects that are created. For performance you should reuse your rows. See the getView implementation here: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List4.html and http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html
You should not create a new View on each call to getView. The convertView that is being passed in allows use to reuse an existing View. In your case this will be an instance of MainView. So you can do something like this:
MainView mv;
if (convertView != null){
mv = (MainView) convertView;
((TextView) mv.findViewById(R.id.title)).setText(articulo.getTitle());
// similar for description and imgUrl
} else {
mv = new MainView(...);
}
return mv;
In addition, you could use the ViewHolder pattern suggested by Michele. This will allow you to avoid the findViewById lookups when setting title etc. Here is a great explanation of ViewHolder.

Best way to handle multiple getView calls from inside an Adapter

I have a ListView with custom ArrayAdapter. Each of the row in this ListView has an icon and some text. These icons are downloaded in background,cached and then using a callback, substituted in their respective ImageViews. The logic to get a thumbnail from cache or download is triggered every time getView() runs.
Now, according to Romain Guy:
"there is absolutely no guarantee on
the order in which getView() will be
called nor how many times."
I have seen this happen, for a row of size two getView() was being called six times!
How do I change my code to avoid duplicate thumbnail-fetch-requests and also handle view recycling?
Thanks.
Exactly, that could happen for example when you have
android:layout_height="wrap_content"
in your ListView definition. Changing it to fill_parent/match_parent would avoid it.
From api.
public abstract View getView (int position, View convertView,
ViewGroup parent)
convertView - The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view.
So if getView has already been called for this specific index then convertView will be the View object that was returned from that first call.
You can do something like.
if(!(convertView instanceof ImageView)){
convertView = new ImageView();
//get image from whereever
} else {} // ImageView already created
I m experiancing the same issue i change the layout_height of listView to match_parent resolve my issue.
My understanding is that you need to use the ViewHolder design pattern here. Just using a returned convertView can lead to reuse of a previous view (with some other image assigned in this case).
public class ImageAdapter extends ArrayAdapter<String> {
// Image adapter code goes here.
private ViewHolder {
public ImageView imageView;
public String url;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = null;
ViewHolder viewHolder;
String url = getUrl(position);
if (convertView == null) {
// There was no view to recycle. Create a new view.
view = inflator.inflate(R.layout.image_layout, parent, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) view.findViewById(R.id.image_view);
viewHolder.url = url;
view.setTag(viewHolder);
} else {
// We got a view that can be recycled.
view = convertView;
viewHolder = ((ViewHolder) view.getTag());
if (viewHolder.url.equals(url)) {
// Nothing to do, we have the view with the correct info already.
return view;
}
}
// Do work to set your imageView which can be accessed by viewHolder.imageView
return view;
}
}
The better would be to create a object with Thumbnail(bitmap) and the text. And read the thumbnail if its not available in the object.
Create an array of ImageView objects in your adapter and cache them as you retrive them (whether from cache or web). For example, in getView, before you fetch the ImageView, check if it's already in your local array, if so, use it, if not fetch, once received store in your local ImageView array for future use.
My Fragment.xml has a ListView, the layout setting of this ListView was android:layout_height="wrap_content", and this ListView will bind to SimpleCursorAdapter later. Then I have the same issue in ViewBinder be called 3 times. The issue resolved after I change the layout_height="wrap_content" to "95p". I do consider that the "wrap_content" height cause this issue.
Trying to modify your Fragment.xml and I guess the 3 times called issue will no longer exist.

Categories

Resources