我正在使用mvvmcross为Android开发应用程序。
在这个应用程序中,我希望有一个包含微调器的列表。当我在模拟器上测试应用程序时它看起来不错,但是当我滚动它时内存很快因为gref超过2000.我知道gref可以在真实设备上更高但我仍然认为我必须做错了
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(剥离)
<?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>
是否发生了这种情况,因为每次滚动时都必须重建微调器项目?因为它所绑定的列表在列表中的每个项目中都是不同的。因此,在一个列表项上,微调器列表可以是六个项目,另一个可以是3个项目,依此类推。
答案 0 :(得分:4)
我还没有对您所看到的行为进行全面分析 - 如果没有完整的代码示例则很难做到。
但是,尤其要感谢JonPryor Xamarin forums我相信我现在至少可以更好地了解一般情况下GREF的情况 - 所以我可以回答你'为什么'这个问题。 / p>
绑定列表的一般情况是GREF递增:
TextView
和Button
,则C#将存储对这些视图的引用在您的示例中,每个列表项本身都包含一个绑定列表 - 这将导致所需GREF数量的倍增 - 这就是您看到报告问题的原因。
有了这种理解,显而易见的问题可能是“我们怎样解决这个问题?”
这不是一个简单的问题,但我认为有一些方法可以解决这个问题。
首先,我们可以与Xamarin讨论这个问题 - 可能是应该增加可用的GREF的数量 - 因为这些对象将在Java中的内存中,那么在C#中引用它们可能没有任何害处好?
其次,我们可以考虑改变实现UI绑定的方式,以便永久引用不会存储到所有对象 - 例如,如果我们知道一次性绑定(例如,对于标签),那么我们可能会看一下不对此功能使用XML数据绑定的路由。例如,我们可以使用新的View手动执行此绑定。
第三,我们可以考虑更改绑定代码本身,以便对于单向绑定(从ViewModel到View),它在更新时使用FindViewById<TView>
检索Android视图,而不是使用对视图的保留引用。这会更慢,但会减少所需的GREF数量。在明确声明的“一次性”绑定的情况下,此功能可能是最可实现的。
第四,这是您作为应用开发者可能最容易访问的解决方案,您可以查看更改UI实现,以便应用不使用这些绑定的子列表 - 例如它可以改为使用标签 - 它只按需创建微调器(通过处理代码中的Click事件)。
我确信除此之外还有其他选择......
在此分析中我问自己的一个问题是,这个问题是否是MvvmCross独有的,或者是否是所有MonoDroid应用程序都可能出现的问题。
我不是百分百肯定,但我认为答案是这是一个影响所有MonoDroid应用程序的一般性问题。但是,我也认为MvvmCross在这个问题上增加了一点:
我也不认为这完全只是一个MvvmCross或MonoDroid问题。虽然由于MonoDroid的实现而突出了这个GREF限制,但这里的根本问题实际上是尝试一次做太多 - 所以你真的可以通过对设计/实现进行流式处理来提高应用程序的性能,以便减少使用观点。虽然它可能不喜欢它,但我认为MonoDroid 帮我们一个忙[/ strong> - 它指出我们的UI实现有点“胖”,我们应该考虑在我们的应用程序代码中优化它。
我会在发现更多信息时更新这个答案,但我希望上述信息已经让您对这种情况的“原因”有了很好的了解。
答案 1 :(得分:3)
(这可能是对Stuart's excellent answer的评论,但我需要更多空间......)
问题出在MvxBindableListAdapter.GetItem():
之内public override Object GetItem(int position)
{
return new MvxJavaContainer<object>(GetSourceItem(position));
}
这个问题是每次调用它都会创建一个新的GREF(因为每次调用都会实例化一个新对象)。 “修复”(解决方法?):Don't Do That™。
例如,ApiDemo/Tutorials/GridViewTutorial.cs示例只返回null
:
public override Java.Lang.Object GetItem (int position)
{
return null;
}
这是有效的,因为没有(后果)调用GetItem()
;相反,GridViewTutorial.cs
会从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;
}
然后是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()
返回一个值,那么您应该确保不会不必要地重新创建值。使用缓存或其他“存储”机制来减少实例化的对象数量。