Monodroid - passing data to ListView according to

2019-09-17 06:01发布

问题:

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 ItemInfo (){                
                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.

回答1:

In my project i had the same problem.

  1. ItemInfo is managed object, so you don't need to do anything with it, GC collects when it will be necessary.
  2. 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;
    }
}


回答2:

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.