I'm having trouble with a listview in android. When I start scrolling down my List, it is very slow and I see that the GC is called. When I'm at the bottom of my List, everything works fine and smooth. I think that at this point my ViewHolder does the work.
But I can't find the source that is calling the GC. I searched which lead to:
DDMS 436816 byte[] 1 android.graphics.Bitmap nativeCreate
I can't interpret that line. My ArrayAdapter and it's getView method looks like this:
public class DiagnoseAdapter extends ArrayAdapter<Visualizer> {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
int type = TYPE_DEFAULT;
final Visualizer item = getItem(position);
switch(item.getType()){
case TYPE_DEFAULT:
convertView = DefaultTextView.getView(position, convertView, mlayoutInflater, item, parent);
break;
// more cases/types
}
return convertView;
}
}
which is calling the following getView Method of the class DefaultTextView
public class DefaultTextView{
public static View getView(int position, View convertView, LayoutInflater layoutInflater, Visualizer item, ViewGroup parent){
ViewHolder holder;
if (convertView == null || item.getReleatedObject() == null || convertView.getTag()!=TAG_DEFAULT) {
convertView = layoutInflater.inflate(R.layout.diagnose_item, null);
holder = new ViewHolder();
holder.value = (TextView) convertView.findViewById(R.id.diagnose_function_value);
holder.name = (TextView) convertView.findViewById(R.id.diagnose_function_setname);
holder.mLinLayout = (LinearLayout) convertView.findViewById(R.id.default_linlayout);
convertView.setTag(TAG_DEFAULT);
convertView.setTag(R.layout.diagnose_item,holder);
item.setReleatedObject(convertView);
} else {
holder = (ViewHolder) convertView.getTag(R.layout.diagnose_item);
}
holder.value.setText(item.toString());
holder.name.setText(item.getToolTip());
holder.mLinLayout.removeAllViews();
if (item.getUpdateFlag(4)) {
if (holder.back == null){
holder.back = new ImageView(convertView.getContext());
holder.back.setScaleType(ScaleType.FIT_CENTER);
holder.back.setAdjustViewBounds(true);
holder.back.setImageBitmap(bm1);
}
holder.mLinLayout.addView(holder.back);
}
if (item.getUpdateFlag(1)) {
if (holder.update == null){
holder.update = new ImageView(convertView.getContext());
holder.update.setScaleType(ScaleType.FIT_CENTER);
holder.update.setAdjustViewBounds(true);
holder.update.setImageBitmap(bm2);
}
holder.mLinLayout.addView(holder.update);
}
if (item.getUpdateFlag(2)) {
if (holder.timer == null){
holder.timer = new ImageView(convertView.getContext());
holder.timer.setScaleType(ScaleType.FIT_CENTER);
holder.timer.setAdjustViewBounds(true);
holder.timer.setImageBitmap(bm3)
}
holder.mLinLayout.addView(holder.timer);
}
if (item.getUpdateFlag(3)) {
if (holder.log == null){
holder.log = new ImageView(convertView.getContext());
holder.log.setScaleType(ScaleType.FIT_CENTER);
holder.log.setAdjustViewBounds(true);
holder.log.setImageBitmap(bm4);
}
holder.mLinLayout.addView(holder.log);
}
if (item.getUpdateFlag(0)) {
if (holder.forward == null){
holder.forward = new ImageView(convertView.getContext());
holder.forward.setScaleType(ScaleType.FIT_CENTER);
holder.forward.setAdjustViewBounds(true);
holder.forward.setImageBitmap(bm5);
}
holder.mLinLayout.addView(holder.forward);
}
return convertView;
}
static class ViewHolder {
TextView name, value;
ImageView back, update, timer, log, forward;
LinearLayout mLinLayout;
}
}
Even if I comment the LinearLayout out, so I just have a List with two TextViews.
So my Question. Do I miss anything. Some stupid thing? How do I get my ListView smoother?
BTW: I read in a different thread, that it is happening if the ListView has the attribute android:cacheColorHint="#00000000. I don't have this attribute.
I hope anyone has a solution. Thanks!
About the source of GC calls. If I'm understanding your code correctly, everytime your ListView items are recycled and you call removeAllViews(), a previously dynamically created ImageView is removed and its Bitmap is garbage collected. So, Maybe those GC calls would be avoided if you use the same ImageView declaring it in your xml layout and just replace the Bitmap according to your getUpdateFlag().
And two more things about ListViews and Images. First thing is that if the image is too big, your ListView is going to be laggy no matter what. You would need to scale the image down if that is the case( Loading Large Bitmaps Efficiently). And second, maybe you would also need to implement a Lazy List, which loads images on demand, there is a famous question about that --> How do I do a lazy load of images in ListView?
I've finally solved my Problem. Like above, I thought the problem was based on the images of my list items. But that wasn't the problem. I just didn't use my ViewHolders and the getItemViewType(int position) method correctly. I have a list with many different item layouts and I saw, that my code above created a new convertView and a new ViewHolder for every single item, which wasn't supposed to be. I found a great tutorial about how to use multiple item layouts (see link below):
Multiple List Item Layouts
Related
I have a list of words. I want the letters of each word in the list to display as images in a gridview.
I have implemented a working version of this by creating a custom adapter, WordListAdapter, for my list of words. For each word, WordListAdapter.getView, in turn, calls a custom adapter, LetterImageAdapter, to display the letters in the words. Since I'm new to Android programming, I'm wondering if this is a good approach. It sure seems like a lot of work. If it is a good approach, perhaps this will be helpful to someone else.
I've seen a number of other posts about gridviews inside listviews. Many of them touched on issues with scrolling and memory but I didn't see anything specific to this use case.
In my activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
ListView listView = (ListView) findViewById(R.id.wordList);
listView.setAdapter(new WordListAdapter(this, new String[]{"ABC", "DEF"}));
}
WordListAdapter.getView()
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.word_grid, null);
GridView gridView = (GridView) convertView.findViewById(R.id.word_grid_id);
holder = new ViewHolder();
holder.gridView = gridView;
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.gridView.setAdapter(new LetterImageAdapter(mContext, words[position]));
return convertView;
}
private static class ViewHolder {
GridView gridView;
}
LetterImageAdapter.getView()
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.letter_item, null);
holder = new ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.list_item_letter_imageview);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// resource for each letter is in lettersId array
holder.icon.setImageResource(letterIds[position]);
return convertView;
}
private static class ViewHolder {
ImageView icon;
}
// transform word into array of resources for each letter
private void buildResourceArray(String packageName) {
letterIds = new int[theWord.length()];
for (int index = 0; index < theWord.length(); index++) {
letterIds[index] = getResourceId(theWord.substring(index, index + 1).toLowerCase(), "drawable", packageName);
}
}
I'd suggest to use a LinearLayout with ImageViews instead of a GridView to hold the letters.
While technically is possible to have a GridView inside a ListView, GridViews are pretty complex objects, better suited to be a main list than a recyclable child item. You will run into complex issues when recycling the main items, as every GridView will have its own adapter.
So as the number of letters is pretty limited, a LinearLayout (maybe inside a HorizontalScrollView) is a far simpler approach. You can even cache the letter bitmaps, or include them as resources, so they are shared by all ImageViews in the LinearLayouts
To do something related to your intentions, you'd better look at RecyclerView, that is kind of an abstract item list container that you can setup to do all kinds of complex layouts. But being a beginner, I'd suggest to first master the simpler ListView / GridView then move into RecyclerView when you understand how View recycling works.
I have a ListView that I put through a complex bit of coding. The list changes often with different types of data that require different views. On rare occasion, I'll end up with 1 view being reused by Android for a row that's supposed to look different. It seems to only happen when the data being displayed radically changes. I was hoping there was a way to programmatically wipe the ListView's memory clean. Is this possible?
Here is the beginning of my getView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
SearchHolder holder = null;
int type = getItemViewType(position);
if (null == convertView) {
holder = new SearchHolder();
if (type == SEARCH_TYPE_FREETEXT) {
convertView = mInflater.inflate(R.layout.layout_search_item_freetext, null);
holder.txtText = (TextView) convertView.findViewById(R.id.search_itemname);
holder.vHeaderWrapper = (LinearLayout) convertView.findViewById(R.id.search_headerwrapper);
holder.txtHeader = (TextView) convertView.findViewById(R.id.search_header);
}
else {
if (items.get(position).mData == null) {
convertView = mInflater.inflate(R.layout.layout_loadmoreresults_white, null);
}
else {
convertView = mInflater.inflate(R.layout.layout_search_item, null);
holder.txtText = (TextView) convertView.findViewById(R.id.search_itemname);
holder.vHeaderWrapper = (LinearLayout) convertView.findViewById(R.id.search_headerwrapper);
holder.txtHeader = (TextView) convertView.findViewById(R.id.search_header);
}
}
convertView.setTag(holder);
}
else {
holder = (SearchHolder)convertView.getTag();
}
...
ListView does something called "recycling" when you scroll through a list, and what you will need to do is override the getView() method to update the individual listView item that is being recycled. By default android does not clear out these views. Check out the following link on ListView recycling:
http://mobile.cs.fsu.edu/the-nuance-of-android-listview-recycling-for-n00bs/
Without code it is hard to tell if you already know about this or not, but this is the cause of such problems in my experience.
If you use more than one layout for your list items then I suggest to inflate the appropriate layout from the xml every time in the getView() method.
I have custom listview. When I scroll my listview, android keeps in memory (as far as I understand) items which is displaying on screen and doesn't keep items which is hidden (not scrolled to).
In my case (I think) keeping all list items would be better than generating hidden items.
So, how to "tell" android to keep all items in memory? (15-20 items). PS: if it's wasting of resources, I'd like just to try.
My adapter (some funcs):
private View newView(Context context, ViewGroup parent) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
return layoutInflater.inflate(R.layout.myl,parent,false);
}
public View getView(int position,View convertView,ViewGroup parent) {
View view=null;
if(convertView!=null) view=convertView; else view=newView(context,parent);
HashMap<String,String> d=new HashMap<String,String>();
d=data.get(position);
String qweqwe=d.get("qweqwe"); //9 more lines like this.
TextView txt=(TextView)view.findViewById(R.id.mfmf); //
txt.setText(qweqwe); //
txt.setTypeface(mlf); //5 more blocks of 3 lines like this.
if (smth.equals("0")){
view.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.mvmv));
} else {
view.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.mvmv2));
}
return view;
}
Ok.. there are some things that can be optimized here, instead of trying to fix lag with workarounds :)
You should implement a static class, where you can store references to the Views in your myl.xml. For each View you want to manipulate in myl.xml, you create a View in this static class. So if you have 10 TextViews, you fill this class with 10 TextViews.
static class AdapterViewsHolder {
TextView txt1;
TextView txt2;
TextView txt3;
...
ImageView img1;
... etc etc.
}
In the adapter, you now only do the findViewById() calls if the convertView is null. findViewById() is not cheap, so limiting the amount of calls increases performance.
private HashMap<String, String> mData;
private LayoutInflater mInflater;
private TypeFace mCustomTypeFace;
// Some contructor for passing data into the Adapter.
public BaseAdapter(HashMap<String, String> data, Context ctx) {
mData = data;
mInflater = LayoutInflater.from(ctx);
mCustomTypeFace = Typeface.createFromAsset(ctx.getAssets(), "yourTypeFace.ttf");
}
public View getView(int position, View convertView, ViewGroup parent) {
// This AdapterViewsHolder will hold references to your views, so you don't have to
// call findViewById() all the time :)
AdapterViewsHolder holder;
// Check if convertView is null
if(convertView == null) {
// If it is, we have to inflate a new view. You can probably use the newView() call here if you want.
convertView = mInflater.inflate(R.layout.myl, null);
// Initialize the holder
holder = new AdapterViewsHolder();
// Now we do the smart thing: We store references to the views we need, in the holder. Just find all the views you need, by id.
holder.txt1 = (TextView) convertView.findViewById(R.id.textview1);
holder.txt2 = (TextView) convertView.findViewById(R.id.textview2);
...
holder.img1 = (ImageView) convertView.findViewById(R.id.imageview1);
// Store the holder in the convertViews tag
convertView.setTag(holder);
}
else {
// If convertView is not null, we can get get the holder we stored in the tag.
// This holder now contains references to all the views we need :)
holder = (AdapterViewsHolder) convertView.getTag();
}
// Now we can start assigning values to the textviews and the imageviews etc etc
holder.txt1.setText(mData.get(position));
...
holder.txt1.setTypeface(mCustomTypeFace);
...
holder.img1.setImageResource("IMAGE RESOURCE HERE");
if(someThing.equals("sometext") {
convertView.setBackgroundDrawable(somedrawable);
}
else {
convertView.setBackgroundDrawable(someotherdrawable);
}
// Finally, we return the convertView
return convertView;
}
I do not know how your data is organized, so you have to change this code a bit.
One more thing that can cause lag is the android:cacheColorHint xml attribute. Usually you set this to either the same color as you application background, or transparent. Setting it transparent have been known to cause rapid Garbage collections on some occasions.
You could override the Adapter and have it inflate all of the views in the constructor, then just return the proper one with getView(). Might make it easy if you store the Views in some data object (array, list etc..)
But really you should let the system use the convertView like it was designed to. Overall you'd get better performance doing it that way I think.
I have a question, maybe a silly one, but I think it is important.
Why the parameter: convertView (View) on the
public View getView(int position, View convertView, ViewGroup parent)
is always null? android is supposed to recycle the views once they're created the first time, isn't it? or how can I do to recycle those views?
I feel like the method receives those 3 parameters, but in none of the google examples they use either of them.
Unfortunately, convertView will always be null, due to Android bug 3376. Gallery does not implement View recycling (at least as of Gingerbread/2.3.4).
A commenter in the bug suggests forking Gallery.java (from AOSP) and implementing it yourself, which may be the best option.
The convertView parameter indeed will be null a few first times, when this function will be called. Then if you scroll the list / galery Android will give you the same view, which was constructed earlier using this function, and you should use it to optimally construct the new view, based on the old one.
Also, you should store the references to child view somewhere.
To better undestand that, look at this code example (taken from Android Developers):
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary calls
// to findViewById() on each row.
ViewHolder holder;
// When convertView is not null, we can reuse it directly, there is no need
// to reinflate it. We only inflate a new View when the convertView supplied
// by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
// Bind the data efficiently with the holder.
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
On getView() method normally you check if convertView is null, and if it isn't you just rewrite the fields in the View adapting it to the data you get based on the position instead of creating a new View (from inflation or whatever method you want).
Hope it helped,
JQCorreia
getView() has a second parameter as view(convertView).This convertView is the view which is returned from previous iteration.For the first iteration it will be null and adapter will create (instance) view.When it is done with creating required layout,the view will be returned to its caller.This returned value will be available as 2nd parameter from the next iteration onwards.So one can decide to reuse previously returned view instead of recreating by looking at this parameter.Thus Android achives re-usability feature while creating multiple list items.
Since convertView will always be null you should implement your own algorithm of caching and reusing items.
This is my implementation of Gallery adapter
public View getView(int position, View convertView, ViewGroup parent) {
int arrPosition = position % VIEW_CHACHE_SIZE;
ImageView imageView;
mCursor.moveToPosition(position);
if (parent.getHeight() > 0 && layoutParams.height == 0) {
layoutParams = new Gallery.LayoutParams(parent.getWidth() / VISIBLE_IMAGES_COUNT, (int) (parent.getHeight() * IMAGE_HEIGHT_COEFICIENT));
viewsList[0].setLayoutParams(layoutParams);
}
if (convertView != null) {
Log.i("GALLERY", "convert view not null");
}
// check views cache
if (viewsList[arrPosition] == null) {
imageView = new ImageView(mContext);
imageView.setPadding(3, 3, 3, 3);
viewsList[arrPosition] = imageView;
} else {
imageView = viewsList[arrPosition];
if (position == arrPosition) {
if (imageView.getDrawable().equals(imagesList.get(position))) {
return imageView;
}
}
}
// check images cache
if (imagesList.get(position) != null) {
imageView.setImageDrawable(imagesList.get(position));
} else {
byte[] photo = mCursor.getBlob(mCursor.getColumnIndex(DataProxy.PHOTO_COLUMN));
imagesList.put(position, new BitmapDrawable(BitmapFactory.decodeByteArray(photo, 0, photo.length)));
imageView.setImageDrawable(imagesList.get(position));
}
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setLayoutParams(layoutParams);
return imageView;
}
.........................................................
private SparseArray<Drawable> imagesList = new SparseArray<Drawable>();
private ImageView[] viewsList = new ImageView[VIEW_CHACHE_SIZE];
private Gallery.LayoutParams layoutParams = new LayoutParams(0, 0);
private static final int VIEW_CHACHE_SIZE = 4;
I load data from Cursor to listview, but my Listview not really display "smooth". The data change when I drag up and down on the scollbar in my ListView. And some items look like duplicate display in my list.
I hava a "complex ListView" (two textview, one imageview) So I used newView(), bindView() to display data. Can someone help me?
I will describe you how to get such issue that you have. Possibly this will help you.
So, in list adapter you have such code:
public View getView(int position, View contentView, ViewGroup arg2)
{
ViewHolder holder;
if (contentView == null) {
holder = new ViewHolder();
contentView = inflater.inflate(R.layout.my_magic_list,null);
holder.label = (TextView) contentView.findViewById(R.id.label);
contentView.setTag(holder);
} else {
holder = (ViewHolder) contentView.getTag();
}
holder.label.setText(getLabel());
return contentView;
}
As you can see, we set list item value only after we have retrieved holder.
But if you move code into above if statement:
holder.label.setText(getLabel());
so it will look after like below:
if (contentView == null) {
holder = new ViewHolder();
contentView = inflater.inflate(R.layout.my_magic_list,null);
holder.label = (TextView) contentView.findViewById(R.id.label);
holder.label.setText(getLabel());
contentView.setTag(holder);
}
you will have your current application behavior with list item duplication.
Possibly it will help.
ListView is a tricky beast.
Your second question first: you're seeing duplicates because ListView re-uses Views via convertView, but you're not making sure to reset all aspects of the converted view. Make sure that the code path for convertView!=null properly sets all of the data for the view, and everything should work properly.
You'll want your getView() method to look roughly like the following if you're using custom views:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final MyCustomView v = convertView!=null ? (MyCustomView)convertView : new MyCustomView();
v.setMyData( listAdapter.get(position) );
return v;
}
If you're not using your own custom view, just replace the call to new MyCustomView() with a call to inflater.inflate(R.layout.my_layout,null)
As to your first question, you'll want to watch Romain's techtalk on ListView performance here: http://code.google.com/events/io/sessions/TurboChargeUiAndroidFast.html
From his talk and in order of importance from my own experience,
Use convertView
If you have images, don't scale your images on the fly. Use Bitmap.createScaledBitmap to create a scaled bitmap and put that into your views
Use a ViewHolder so you don't have to call a bunch of findViewByIds() every time
Decrease the complexity of the views in your listview. The fewer subviews, the better. RelativeLayout is much better at this than, say, LinearLayout. And make sure to use if you're implementing custom views.
I'm facing this problem as well, but in my case I used threads to fetch the external images. It is important that the current executing thread do not change the imageView if it is reused!
public View getView(int position, View vi, ViewGroup parent) {
ViewHolder holder;
String imageUrl = ...;
if (vi == null) {
vi = inflater.inflate(R.layout.tweet, null);
holder = new ViewHolder();
holder.image = (ImageView) vi.findViewById(R.id.row_img);
...
vi.setTag(holder);
} else {
holder = (ViewHolder) vi.getTag();
}
holder.image.setTag(imageUrl);
...
DRAW_MANAGER.fetchDrawableOnThread(imageUrl, holder.image);
}
And then on the fetching thread I'm doing the important check:
final Handler handler = new Handler() {
#Override
public void handleMessage(Message message) {
// VERY IMPORTANT CHECK
if (urlString.equals(url))
imageView.setImageDrawable((Drawable) message.obj);
};
Thread thread = new Thread() {
#Override
public void run() {
Drawable drawable = fetchDrawable(urlString);
if (drawable != null) {
Message message = handler.obtainMessage(1, drawable);
handler.sendMessage(message);
}
}};
thread.start();
One could also cancel the current thread if their view is reused (like it is described here), but I decided against this because I want to fill my cache for later reuse.
Just one tip: NEVER use transparent background of item layout - it slows performance greatly
You can see the reocurring text in multiple rows if you handle it in the wrong way. I've blogged a bit about it recently - see here. Other than that you might want to take a look at ListView performance optimization. Generally it's because of the view reuse and I've seen it few times already.