Not using setTag() right, seems to block gc?
I'm using the view holder pattern with my ListView. When I run my app for some time, I see that I have lots of ViewHolder instances retained:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.foo, null);
holder = new ViewHolder();
holder.username = (TextView) convertView.findViewById(R.id.username);
holder.address = (TextView) convertView.findViewById(R.id.address);
convertView.setT开发者_运维百科ag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
...
return convertView;
}
private static class ViewHolder {
TextView username;
TextView address;
}
I'm using the UI monkey tool to run batches of 5,000 random operations. The first run, I see about 6 ViewHolder instances in memory. Then about 10, then 15, and so on. I'm seeing these through the memory analyzer tool.
I'm not sure why these additional instances are not being cleaned up. Each ViewHolder instance is retaining "mTag", which then has a large tree under it (the entire view).
Has anyone else seen this?
------------------ Update -------------------------
Still haven't had any luck finding out what's going on. In the Memory Analyzer Tool, I see references I don't understand to the view holder instances (there are currently about 21 instances, while my screen can only fit about 6 at any given time). With the incoming references shown, they look something like this:
com.me.project.MyAdapter$ViewHolder
|-- mTag
| |-- mParent (TextView)
| |-- mParent (TextView)
| |-- [1] java.lang.Object[12]
| |-- array (mCurrentScrap android.widget.AbsListView$RecycleBin)
|
com.me.project.MyAdapter$ViewHolder
|-- mTag (this one looks ok, only lists the two TextView references)
|
com.me.project.MyAdapter$ViewHolder
|-- mTag ..
|-- [1] android.view.View[12]
|-- mChildren android.widget.ListView
|-- this$0 android.widget.ListView$FocusSelector
|-- this$0 android.widget.AdapterView$AdapterDataSetObserver
...
So some instances have only connections to the member variables of ViewHolder (makes sense), others have connections to other data that seems to be part of the parent ListView, but not related to the ViewHolder itself. These don't seem to be getting cleaned up though, and after some time I'll have dozens of these hanging around.
Just to be clear, out of the 20 or so instances I currently had in my most recent mem snapshot, I'll have more than one instance looking like each of those parent nodes above (so multiple instances have the ListView$FocusSelector child under it, etc).
Thanks
You create a new ViewHolder
each time you create a new View
for an Adapter
item, so the right question is How many Views am I creating before my Adapter starts to reuse them? You can simply check this with adding logs "Creating a new view for position " + position and "reusing a view for position " + position.
However, I believe that everything is ok, and you actually have as much new Views as your adapter needs (as much list items as can fit on the screen). If that's not the case (and you are actually getting ridiculous numbers like 100), check if you have many Adapters, for example creating a new one on each onResume()
but still holding a reference to the old ones. Which, is highly unlikely that you have done without noticing, so the number of ViewHolders you have probably corresponds correctly to the number of new Views for your list.
You are missing the following line within your if (convertView == null)
statement:
convertView.setTag(holder);
The key concept to understand is that ListView uses a recycler, so it only wants to instantiate as many views that can fit onto the screen. As a view gets moved off the screen, the recycler is passing it to getView
and asking you to re-populate it. It will then move that same View, with the new data, into the correct position (top or bottom of the list).
精彩评论