.NET MAUI CollectionView内存占用过高问题求助
核心问题1:CollectionView被ScrollView包裹
你的布局中CollectionView嵌套在ScrollView内部,这会直接禁用CollectionView的UI虚拟化功能。CollectionView原本只会渲染当前可见区域的项,剩余项在滚动时按需加载以节省内存,但外层ScrollView会强制CollectionView渲染所有1000+项的完整控件树(每个项包含Button、Entry等控件),这是内存占用暴涨至1.4GB的主要原因。
修复方法:移除外层ScrollView,让CollectionView直接作为布局的一部分:
<!-- 修改后的MainPage.xaml --> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Ö3_Playlist" x:Class="Ö3_Playlist.MainPage"> <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Start"> <Button x:Name="CounterBtn" Text="Parse" SemanticProperties.Hint="Counts the number of times you click" Clicked="OnCounterClicked" HorizontalOptions="Center" /> <ActivityIndicator IsVisible="{Binding IsBusy}" Color="White"/> <!-- 直接放置CollectionView,移除外层ScrollView --> <CollectionView ItemsSource="{Binding Entries}" VerticalOptions="FillAndExpand"> <CollectionView.ItemTemplate> <DataTemplate x:DataType="local:SongEntry"> <HorizontalStackLayout HorizontalOptions="FillAndExpand"> <Button Text="Open" Command="{Binding OpenCommand}"/> <Entry Text="{Binding Name}" IsReadOnly="True"/> <Button Text="{Binding IsLiked}" Command="{Binding ClickedCommand}"/> </HorizontalStackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </VerticalStackLayout> </ContentPage>
核心问题2:使用List而非ObservableCollection
当前Entries使用List<SongEntry>,虽然通过[ObservableProperty]标记,但List<T>不实现INotifyCollectionChanged接口。这意味着向列表添加单个项时,CollectionView无法高效增量更新UI,可能导致不必要的整体刷新,间接增加内存开销。
修复方法:将Entries改为ObservableCollection<SongEntry>:
public partial class ViewModel : ObservableObject { [ObservableProperty] // 替换List为ObservableCollection ObservableCollection<SongEntry> entries = new ObservableCollection<SongEntry>(); // ... 其他代码不变 }
其他优化点
1. 移除ViewModel的析构函数
析构函数(~ViewModel())的执行时机由CLR垃圾回收器决定,无法保证数据及时保存,还可能导致ViewModel对象被延迟回收,增加内存占用。建议在页面卸载时触发保存操作:
public partial class MainPage : ContentPage { public ViewModel vm { get; set; } = new ViewModel(); public MainPage() { BindingContext = vm; InitializeComponent(); // 页面卸载时保存数据 this.Unloaded += (s, e) => Database.Save(vm.Entries.ToList()); } // ... 其他代码不变 }
2. 优化重复项检查逻辑
当前Parse方法中每次添加新项都调用Entries.FirstOrDefault遍历整个列表,当列表有1000+项时,这是O(n²)的耗时操作。可以用HashSet<string>缓存已存在的歌曲名称,将检查复杂度降为O(1):
public async Task Parse() { IsBusy = true; var loader = new WebLoader(); // 缓存已存在的歌曲名称(小写去重) var existingNames = new HashSet<string>(Entries.Select(x => x.Name.Trim().ToLower())); for (int i = 0; i < 10; i++) { var doc = await loader.LoadWebpage(@"https://onlineradiobox.com/at/hitradiooe3/playlist/" + i); var list = GetSongs(doc); foreach (var song in list) { var key = song.Name.Trim().ToLower(); if (!existingNames.Contains(key)) { Database.Insert(song); Entries.Add(song); existingNames.Add(key); } } } IsBusy = false; }
3. 避免async void命令
SongEntry中的Open命令使用async void,这可能导致未捕获的异常和潜在的内存泄漏。建议改为async Task:
[RelayCommand] private async Task Open() { string uri = @"https://www.youtube.com/results?search_query=" + Name; await BrowserOpen_Clicked(uri); }
内容的提问来源于stack exchange,提问作者Thorsten Schmitz

