C#, Mono for Android. I need to output a large portion of combined data into ListView. To achieve this, I use the following obvious approach with Adapter:
class ItemInfo
{
public string Id;
public string Name;
public string Description;
public int Distance;
//Some more data
}
class ItemAdapter : ArrayAdapter<ItemInfo>
{
public WorldItemAdapter (Context context, int textViewResourceId, List<WorldItemInfo> items) :
base(context, textViewResourceId, items)
{
}
//...
public override View GetView (int position, View convertView, ViewGroup parent)
{
//Some stuff to format ListViewItem
}
}
public class OutputActivity : Activity
{
ListView _listView;
//...
void FillList (object SomeParameters)
{
var adaptedList = someDataSource.Where().Join().Union().//anything can be imagined
.Select ((item, item2, item3) =>
new It开发者_StackOverflow社区emInfo (){
Id = item.Id,
Name = item.Name,
Description = String.Format(..., item2, item3),
Distance = ...,
//so on
}
).OrderBy ((arg) => arg.Name).ToList ();
_listView.Adapter = new ItemAdapter (this, Resource.Layout.ListItemFormat, adaptedList ());
}
}
This works very fine... until I start to refresh my ListView frequently. If I generate many ItemInfo's (by refreshing my view, for example), I reach GREF limit soon (described here, "Unexpected NullReferenceExceptions" section), and my application crashes. Looking into Android log, I can see thousands of Android.Runtime.IJavaObject objects, which overflow GREF limit.
According to concepts of Mono VM + Dalvik VM bridge I can understand, that my ItemInfo's need to be passed to Dalvik VM, wrapped to IJavaObject and to be formatted in ListView by native environment - this creates GREF's. As long as garbage collecting is a non-determined process, if I call FillList() many times, old, already used ItemInfo's stay into memory, leaking.
How can I avoid leaking? Or, possible, is there another way to output large portions of formatted data into ListView? I tried:
- I can't reduce the number of ItemInfo's, as long as I need to place my data somehow.
- I can't follow this advice, as long as my ItemInfo is not an IJavaObject.
- As a temporarily solution, I call GC.Collect() every time I need to refresh list, but this looks not a clean way. Also, if I need to output more than 2к objects into list, this doesn't help.
In my project i had the same problem.
- ItemInfo is managed object, so you don't need to do anything with it, GC collects when it will be necessary.
- ListView does not load view for each list item at once, so you can control number of created views and dispose them.
Here is my solution
I have removed some unnecessary overrides from BaseAdapter so don't be afraid if it ask you to implement them.
class CustomViewAdapter : BaseAdapter<ItemInfo>
{
private readonly Context _context;
private IList<ItemInfo> _items;
private readonly IList<View> _views = new List<View>();
public CustomViewAdapter(IntPtr handle)
: base(handle)
{
}
public CustomViewAdapter (Context context, IList<ItemInfo> objects)
{
_context = context;
_items = objects;
}
private void ClearViews()
{
foreach (var view in _views)
{
view.Dispose();
}
_views.Clear();
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var inflater = LayoutInflater.From(_context);
var row = convertView ?? inflater.Inflate(Resource.Layout.ListItemView, parent, false);
/// set view data
if(!_views.Contains(row))
_views.Add(row);
return row;
}
public override int Count
{
get { return _items == null ? 0 : _items.Count; }
}
public override void Dispose()
{
ClearViews();
base.Dispose();
}
}
Usage example
[Activity]
public class MessagesActivity : Activity
{
private CustomViewAdapter _adapter;
protected override void OnCreate(Android.OS.Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.ListView);
_adapter=new CustomViewAdapter(this,Enumerable.Empty<ItemInfo>);
FindViewById<ListView>(Resource.Id.list).Adapter=_adapter;
}
public override void Finish()
{
base.Finish();
if(_adapter!=null)
_adapter.Dispose();
_adapter=null;
}
}
Answer found. I've inherited my ItemInfo class from Java.Lang.Object explicitly and now can call Dispose() when object is no longer needed. This kills leaking GREFs.
精彩评论