3

我正在使用 mvvmcross 为 Android 开发一个应用程序。

在这个应用程序中,我想要一个包含微调器的列表。当我在模拟器上测试应用程序时看起来不错,但是当我滚动它时,它很快就会耗尽内存,因为 gref 超过 2000。我知道 gref 在真实设备上可以更高,但我仍然认为我一定做错了什么.

可绑定列表

    <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(剥离)

<?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>

发生这种情况是因为每次滚动时都必须重建微调器项目吗?因为它绑定到的列表在列表中的每个项目中都是不同的。因此,在一个 listitem 上,微调器列表可以是 6 个项目,而另一个可以是 3 个项目,依此类推。

4

2 回答 2

4

我还没有对您所看到的行为进行全面分析 - 如果没有完整的代码示例,很难做到。

但是,特别感谢Xamarin 论坛上的 JonPryor,我相信我现在至少对一般情况下 GREF 发生的事情有了更好的了解 - 所以我可以回答你的“为什么”问题。


绑定列表的一般情况是 GREF 递增:

在您的示例中,每个列表项本身将包含一个绑定列表 - 这将导致所需 GREF 数量的倍增 - 这就是您看到报告的问题的原因。


有了这种理解,显而易见的问题可能是“我们如何解决这个问题?”

这不是一个简单的问题要回答,但我认为有几种方法可以解决这个问题。

首先,我们可以与 Xamarin 讨论这个问题 - 可能应该增加可用 GREF 的数量 - 因为这些对象在 Java 中将位于内存中,那么在 C# 中引用它们是否也没有什么坏处?

其次,我们可以考虑改变我们的 UI 绑定的实现方式,以便不会将永久引用存储到所有对象 - 例如,如果我们一次性知道绑定(例如标签),那么我们可能会查看一个路由不为此功能使用 XML 数据绑定。例如,我们可以使用新视图手动执行此绑定。

第三,我们可以考虑更改绑定代码本身,以便对于单向绑定(从 ViewModel 到 View),它FindViewById<TView>在更新时使用而不是使用对视图的保留引用来检索 Android 视图。这会更慢,但会减少所需的 GREF 数量。在明确声明的“一次性”绑定的情况下,此功能可能最容易实现。

第四,这可能是您作为应用程序开发人员最容易使用的解决方案,您可以查看更改 UI 实现,以便应用程序不使用这些绑定的子列表 - 例如,它可以改为使用标签 - 这只会创建按需微调器(通过处理代码中的 Click 事件)。

我敢肯定,除此之外还有其他选择...


在分析过程中我问自己的一个问题是这个问题是否是 MvvmCross 独有的,或者它是否是所有 MonoDroid 应用程序都可能出现的问题。

我不能 100% 确定,但我认为答案是,这是一个会影响所有 MonoDroid 应用程序的普遍问题。但是,我也认为 MvvmCross 增加了一点问题:

  • 通过坚持对诸如 TextViews/labels 之类的东西的引用
  • 通过使您更容易编写引用大量 Java 对象的代码。

我也不认为这完全只是一个 MvvmCross 或 MonoDroid 问题。虽然由于 MonoDroid 的实现而突出显示了此 GREF 限制,但这里的根本问题实际上是一次尝试做太多事情 - 所以实际上您可以通过简化设计/实现来提高应用程序的性能,从而减少使用意见。虽然它可能感觉不像,但我认为 MonoDroid在这里帮了我们一个忙——它指出我们的 UI 实现有点“胖”,我们应该考虑在我们的应用程序代码中优化它。


当我发现更多信息时,我会更新这个答案,但我希望上述信息已经让您很好地了解这种情况的“原因”。

于 2012-12-21T18:16:32.340 回答
3

(这可能是对Stuart 出色答案的评论,但我需要更多空间......)

问题出在MvxBindableListAdapter.GetItem()中:

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

这样做的问题是每次调用它都会创建一个新的 GREF(因为每次调用都会实例化一个新对象)。“修复”(解决方法?):不要这样做™</a>。

例如,ApiDemo/Tutorials/GridViewTutorial.cs示例简单地返回null

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

这是有效的,因为没有(后果)调用GetItem(); 相反,从GridViewTutorial.ImageAdapter.GetView()GridViewTutorial.cs返回一个(可能重复使用的)值:

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;
}

然后是LabelledSections/SeparatedListAdapter.cs示例,它“缓存”返回的值SeparatedListAdapter.GetItem()(值实际上是在“其他地方”创建的):

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;
}

这些值存储在SeparatedListAdapters.sections字典中。

实现BaseAdapter.GetItem()的时候,首先要问“调用者需要这个值吗?” 由于调用者通常是您自己的代码,您可以“跳过”该GetItem()方法(让它返回null)并使用另一种机制从适配器获取托管数据。

如果确实需要从 中返回值BaseAdapter.GetItem(),则应确保不会不必要地重新创建值。使用缓存或其他“存储”机制来减少实例化对象的数量。

于 2012-12-21T19:26:35.230 回答