Why does the gref go too high when I put a MvxBindableSpinner in a MvxBindableListView?

依然范特西╮ 提交于 2020-01-02 08:15:30

问题


I'm developing an app for Android using mvvmcross.

In this application I want to have a list which contains a spinner. It looks ok when I test the app on the emulator, but when I scroll it goes out of memory quickly because the gref goes above 2000. I know the gref can go higher on a real device but I still think I must be doing something wrong.

BindableList

    <cirrious.mvvmcross.binding.android.views.MvxBindableListView
          android:id="@+id/propertyHolder"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:layout_below="@id/obsBtLayout"
          android:layout_above="@id/photoframe"          
          local:MvxBind="
          {
            'ItemsSource':{'Path':'PPHolders'},
            'ItemClick':{'Path':'PropertyClickedCommand'}
          }"
          local:MvxItemTemplate="@layout/listitem_property"
        />

ListItem_Property.axml (stripped)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"              
  xmlns:local="http://schemas.android.com/apk/res/AIPApp.UI.Droid"
  android:orientation="horizontal"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:background="@drawable/ListItemSelector"           
  android:descendantFocusability="beforeDescendants"
  >

  <cirrious.mvvmcross.binding.android.views.MvxBindableSpinner
    android:layout_gravity="center_horizontal"
    android:layout_width="200dip"
    android:layout_height="wrap_content"    
    local:MvxDropDownItemTemplate="@layout/spinneritem_propdropdown"
    local:MvxItemTemplate="@layout/spinneritem_prop"
    local:MvxBind="
    {
      'ItemsSource':{'Path':'CodeTableValues'},      
      'SelectedItem':{'Path':'ObservedCodeTable'},
      'Visibility':{'Path':'IsCodeTableValue','Converter':'Visibility'}
    }"/>     

</LinearLayout>

Is this happening because the spinner items have to be rebuild every time I scroll? Because the list it's bound to is different in every item on the list. So on one listitem the spinner list can be six items long on another it can be 3 items long and so on.


回答1:


I've not yet got to a full analysis of the behavior you are seeing - without a full sample of your code it is very hard to do.

However, thanks especially to JonPryor on the Xamarin forums I believe I do now at least have a better understanding of what is happening to GREFs in the general case - and so I can answer your 'why' question.


The general case for bound lists is that the GREFs are incremented:

  • once for every set of bindings - because the bindings are stored in a hybrid C#/Java container object - https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.Binding.Droid/MvxJavaContainer.cs
  • once for every ListView item - because that is used in the list
  • once for every View object within the ListView which has a bound property or event - e.g. if you are binding to a TextView and a Button within each ListView item, then the C# will store references to these views

In your example, each list item will itself contain a bound list - and this will lead to multiplication of the number of GREFs required - which is why you are seeing the problems reported.


With this understanding in place, the obvious question might be 'how can we solve this?'

That's not a simple question to answer, but I think there are a few ways we might be able to tackle the problem.

Firstly, we could talk with Xamarin about this issue - it may be that the number of available GREFs should be increased - since these objects will be in memory in Java, then perhaps there is no harm with them being referenced in C# as well?

Secondly, we could look at changing the way our UI binding is implemented so that permanent references are not stored to all objects - e.g if we know a binding in one-time (e.g. for a label), then we might look at a route which does not use XML data-binding for this functionality. For example, we could use a new View to perform this binding manually.

Thirdly, we could look at changing the binding code itself so that for one-way binding (from ViewModel to View) it retrieves the Android Views using FindViewById<TView> at update time instead of using retained references to the views. This would be slower, but would reduce the number of GREFs required. This functionality might be most achievable in the case of explicitly stated 'one-time' bindings.

Fourthly, this is the solution which might be most accessible to you as an app developer, you could look at changing the UI implementation so that the app doesn't use these bound sublists - e.g. it could instead use a label - which only creates the spinner on-demand (by handling the Click event in your code).

I'm sure there are other options beyond this too...


One question I asked myself during this analysis is whether this problem is unique to MvvmCross, or whether it is a problem which might be seen across all MonoDroid applications.

I'm not 100% sure, but the answer, I think, is that this a general issue which would affect all MonoDroid apps. However, I also think that MvvmCross has added a little to the problem:

  • by holding on to references of things like TextViews/labels
  • by making it easier for you to write code which does reference a lot of Java objects.

I also don't think this is entirely just an MvvmCross or MonoDroid problem. While this GREF limit is being highlighted because of MonoDroid's implementation, the underlying issue here is really one of trying to do too much at one time - so really you could improve your app's performance by stream-lining the design/implementation so that it uses less views. While it may not feel like it, I think MonoDroid is doing us a favour here - it's pointing out that our UI implementation is a bit 'fat' and we should look at optimising it in our app code.


I will update this answer as I find out more, but I hope the above information already gives you quite a good insight into the 'why' of this situation.




回答2:


(This could be a comment to Stuart's excellent answer, but I needed more room...)

The problem is within MvxBindableListAdapter.GetItem():

public override Object GetItem(int position)
{
    return new MvxJavaContainer<object>(GetSourceItem(position));
}

The problem with this that it'll create a new GREF every time it is invoked (because a new object is instantiated on every invocation). The "fix" (workaround?): Don't Do That™.

For example, the ApiDemo/Tutorials/GridViewTutorial.cs sample simply returns null:

public override Java.Lang.Object GetItem (int position)
{
    return null;
}

This works because nothing (of consequence) calls GetItem(); instead, GridViewTutorial.cs returns a (possibly reused) value from GridViewTutorial.ImageAdapter.GetView():

public override View GetView (int position, View convertView, ViewGroup parent)
{
    ImageView imageView;

    if (convertView == null) {  // if it's not recycled, initialize some attributes  
        imageView = new ImageView (context);
        imageView.LayoutParameters = new GridView.LayoutParams (85, 85);
        imageView.SetScaleType (ImageView.ScaleType.CenterCrop);
        imageView.SetPadding (8, 8, 8, 8);
    } else {
        imageView = (ImageView)convertView;
    }

    imageView.SetImageResource (thumbIds[position]);
    return imageView;
}

Then there's the LabelledSections/SeparatedListAdapter.cs sample, which "caches" the value returned by SeparatedListAdapter.GetItem() (values are actually created "elsewhere"):

public override Java.Lang.Object GetItem (int position)
{
    int op = position;
    foreach (var section in sections.Keys) {
        var adapter = sections [section];
        int size = adapter.Count + 1;
        if (position == 0)
            return section;
        if (position < size)
            return adapter.GetItem (position - 1);
        position -= size;
    }
    return null;
}

The values are stored in the SeparatedListAdapters.sections dictionary.

When implementing BaseAdapter.GetItem(), you should first ask "Does the caller need the value?" Since the caller is often your own code, you may be able to "skip" the GetItem() method (have it return null) and use another mechanism to obtain managed data from the Adapter.

If you do need to return a value from BaseAdapter.GetItem(), then you should ensure that you don't needlessly recreate values. Use a cache or other "storage" mechanism to reduce the number of objects which are instantiated.



来源:https://stackoverflow.com/questions/13842864/why-does-the-gref-go-too-high-when-i-put-a-mvxbindablespinner-in-a-mvxbindableli

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!