Проблема с памятью в списках форм xamarin

#c# #xamarin #xamarin.forms

#c# #xamarin #xamarin.forms

Вопрос:

Я создал простой проект для изучения проблемы с производительностью в моем Xamarin.Формирует приложение.

У меня есть два вложенных списка для классической структуры с основными деталями. В соответствии с шаблоном MVVM я реализовал интерфейс INotifyPropertyChanged для классов ViewModel и использовал ObservableCollection для списков. В главном представлении у меня есть два стандартных ListView, первый (masters) привязан к основной коллекции, второй (details) привязан к коллекции details выбранного элемента основного списка. Оба имеют CachingStrategy=»RecycleElement».

При запуске приложения UWP в инструментах диагностики Visual Studio можно увидеть использование памяти приложением. При выборе элемента списка мастеров отображается список сведений и увеличивается объем используемой памяти, при изменении выбранного элемента списка мастеров список сведений пополняется соответствующими элементами и используемая память увеличивается, также, если для отображения меньше элементов, если снова выбран предыдущий элемент мастеров, используемая память снова увеличивается. Каждый раз, когда в главном списке изменяются выбранные элементы и обновляется список сведений, используемая память увеличивается, никогда не уменьшаясь. Похоже, что объекты, используемые для рендеринга элементов списка сведений, никогда не собираются GC.

Я думаю, что это слишком очевидный эффект, чтобы быть ошибкой, которую никто никогда не замечал, но тестовый проект очень минимален, все viewmodels наследуются от класса MVVMLight ViewModelBase, все свойства используют метод Set, а тип всех свойств списка — ObservableCollection , единственный пользовательский код предназначен для начальной загрузки. Я думаю, что я совершаю ошибку, которую не вижу.

Ниже представлены модели просмотра и код просмотра

MainViewModel.cs

 public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        this.Load();
    }

    private RelayCommand _reloadCommand;
    public RelayCommand ReloadCommand { get { return _reloadCommand ?? (_reloadCommand = new RelayCommand(this.Load)); } }

    private ObservableCollection<ItemViewModel> _items;
    public ObservableCollection<ItemViewModel> Items { get { return _items; } set { this.Set(ref _items, value); } }

    private ItemViewModel _selectedItem;
    public ItemViewModel SelectedItem { get { return _selectedItem; } set { this.Set(ref _selectedItem, value); } }

    private SubitemViewModel _selectedSubitem;
    public SubitemViewModel SelectedSubitem { get { return _selectedSubitem; } set { this.Set(ref _selectedSubitem, value); } }

    private void Load()
    {
        int[] ranges = { 15, 10, 5, 20 };

        this.Items = new ObservableCollection<ItemViewModel>(
            from i in Enumerable.Range(1, 15)
            select new ItemViewModel()
            {
                Label = $"Item {i}",
                Items = new ObservableCollection<SubitemViewModel>(from s in Enumerable.Range(1, ranges[i % 4] )
                        select new SubitemViewModel() { Label = $"Subitem {i}.{s}" })
            });
    }
}
  

ItemViewModel.cs

 public class ItemViewModel : ViewModelBase
{
    private string _label;
    public string Label { get { return _label; } set { this.Set(ref _label, value); } }

    private ObservableCollection<SubitemViewModel> _items;
    public ObservableCollection<SubitemViewModel> Items { get { return _items; } set { this.Set(ref _items, value); } }
}
  

SubItemViewModel.cs

 public class SubitemViewModel : ViewModelBase
{
    private string _label;
    public string Label { get { return _label; } set { this.Set(ref _label, value); } }
}
  

MainView.xaml

 <?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:App1.ViewModel"
             x:Class="App1.MainPage">
    <StackLayout>
        <Grid Padding="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="3*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto" />
                <RowDefinition Height="*" />
                <RowDefinition Height="auto" />
            </Grid.RowDefinitions>

            <Label Grid.Row="0" Grid.Column="0" Text="Masters" />
            <Label Grid.Row="0" Grid.Column="1" Text="Details" />

            <ListView Grid.Column="0" Grid.Row="1"
                      ItemsSource="{Binding Items}"
                      CachingStrategy="RecycleElement"
                      SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="vm:ItemViewModel">
                        <ViewCell>
                            <ViewCell.View>
                                <Label Grid.Row="0" Grid.Column="1" Text="{Binding Label}" />
                            </ViewCell.View>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

            <ListView Grid.Column="1" Grid.Row="1"
                      ItemsSource="{Binding SelectedItem.Items}"
                      SelectedItem="{Binding SelectedSubitem, Mode=TwoWay}"
                      CachingStrategy="RecycleElement">
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="vm:SubitemViewModel">
                        <ViewCell>
                            <ViewCell.View>
                                <Label Grid.Row="0" Grid.Column="1" Text="{Binding Label}" />
                            </ViewCell.View>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </StackLayout>
</ContentPage>
  

Полный тестовый проект можно скачать здесь:MemoryTest.zip

Комментарии:

1. Действительно ли это проблема? Можете ли вы показать некоторые реальные цифры? Если использование памяти увеличивается на несколько КБ за раз, GC просто не хочет включаться. В любом случае, это не утечка памяти, это управляемый код.

2. Запустите 51 МБ Элемент1-> 10 подпунктов 53 МБ Элемент2-> 5 подпунктов 53 МБ Элемент3-> 20 подпунктов 56 МБ Элемент4-> 15 подпунктов 58 МБ Элемент3->20 подпунктов 60 МБ Каждый элемент Элемент3 выбран, используемая память увеличивается на 2 МБ

3. Как @CodeCaster , сначала попробуйте принудительно выполнить сборку мусора и посмотрите, что произойдет. Если произошла утечка памяти, вы используете какой-то плагин MVVM, и может случиться так, что он протекает. Попробуйте использовать только встроенную в Xamarin MVVM.

4. Вероятно, утечка памяти — не самый лучший термин, но использование памяти растет, но никогда не уменьшается. Это означает, что некоторые объекты, которые, как я ожидаю, больше не будут использоваться, остаются используемыми и не могут быть освобождены GC. Я попытался заставить GC. При сборе в наборе SelectedItem инструменты производительности отображают событие GC, но использование памяти не изменяется. Я сомневаюсь, что в такой часто используемой библиотеке, как MVVMLight, есть такая ошибка, но я попытался напрямую реализовать INotifyPropertyChanged… То же поведение.

5. @MrWolf Привет, вот обсуждение управления памятью форм Xamarin: forums.xamarin.com/discussion/23097 /…