MAUI安卓应用运行一段时间后卡顿崩溃问题求助
MAUI Android Release模式下应用卡顿/崩溃问题排查与解决
问题描述
应用迁移至MAUI框架后,在Android设备或模拟器的Release模式下,运行一段时间会逐渐变慢,最终出现卡顿甚至崩溃,问题多发生在点击按钮执行操作并跳转页面时。调用GC.Collect()可避免崩溃,但会引发应用卡顿,且AppCenter未捕获到相关崩溃或卡顿日志。
相关代码
1. 按钮点击触发的ProcesRecord方法
private async Task ProcesRecord() { Stopwatch watch = new Stopwatch(); try { watch.Start(); if (_qtyRemain > 0) { Reset(); return; } IsBusy = true; var dtSave = new DataTable("item_loc"); dtSave.Columns.Add("id", typeof(string)); if (_isInd) { dtSave.Columns.Add("code", typeof(string)); } var dtHold = new List<Test>(); if (!_isInd) { foreach (var lvItem in Results) { if (!(lvItem.qty > 0)) continue; var dr = dtSave.NewRow(); dr["id"] = _itemId; dtSave.Rows.Add(dr); } } else { var results = (string.IsNullOrEmpty(_Id)) ? Results.ToList() : ItemList; foreach (var lvItem in results ?? []) { if (string.IsNullOrEmpty(Id) && !(lvItem.qty_assigned > 0)) continue; var dr = dtSave.NewRow(); dr["id"] = lvItem.item_id.Trim(); } var dsSave = new DataSet("item_loc"); dsSave.Tables.Add(dtSave); dsSave.AcceptChanges(); dsSave.Dispose(); dtSave.Dispose(); dsSave = null; dtSave = null; var msg = await new ApiController().UpdateItem(model); if (msg?.ToLowerInvariant() != "true") { string? errorMesg = msg?.Substring(msg.IndexOf("Message:")).Replace("Message:", ""); if (errorMesg != null) await DisplayAlert("Item", errorMesg.Trim(), "Ok"); } else { switch (_itemType) { case Type.Item: { var frm = new ItemTabbedPage(_itemType); await GoTo(frm); break; } case Type.Accepted: { if (_parentForm != null && _parentForm == "Receive") { var frm = new ReceivePage(); await GoTo(frm); } else { var navigation = Application.Current?.MainPage?.Navigation; if (navigation?.NavigationStack[navigation.NavigationStack.Count - 2] is AssignPage previousPage) { navigation.RemovePage(previousPage); } var tabbedPage = new CVAwayPage(); if (tabbedPage.Children[0] is CVReadyPage cvReadyPage) { if (_sendSeaBack) { tabbedPage.Children[0] = new CVReadyPage(_itemId); } else { tabbedPage.Children[0] = new CVReadyPage(); } } await Application.Current!.MainPage!.Navigation.PushAsync(tabbedPage); } break; } default: throw new ArgumentOutOfRangeException(); } } } catch (Exception ex) { await DisplayErrorAlert(ex.Message); ReportError("Items: Failed.", ex); } finally { if (watch.IsRunning) { watch.Stop(); string paramvalue = "Test"; string count = Results != null ? Results.Count.ToString() : "0"; int success = await InsertExecutionLogDetails("Items", paramvalue, count, watch.ElapsedMilliseconds.ToString()); } } }
2. 跳转目标页面的OnAppearing方法
protected override void OnAppearing() { try { base.OnAppearing(); ViewModel?.OnAppearing(); SetupListeners(); GC.Collect(); GC.WaitForPendingFinalizers(); } catch (Exception ex) { ReportError("AssignItemPage.OnAppearing: Failed.", ex); DisplayErrorAlert(ex.Message); } }
3. 页面中DataGrid的使用代码
<StackLayout Grid.Row="7" Orientation="Vertical" Margin="0,0,0,0"> <dg:DataGrid x:Name="Grid" ItemsSource="{Binding Results}" HeightRequest="230"> <dg:DataGrid.NoDataView> <Label Text="No item locations found, please setup new item location(s)." TextColor="Crimson" HorizontalTextAlignment="Center" /> </dg:DataGrid.NoDataView> <dg:DataGrid.Columns> <helpers:DataGridColumn Title="Location" Width="100" PropertyName="location_label"/> <helpers:DataGridColumn Title="Cur.Qty" Width="80" PropertyName="total_qty" /> <helpers:DataGridColumn Title="Asnd." Width="80" PropertyName="qty_assigned"/> <helpers:DataGridColumn Title="Status" Width="100" PropertyName="inventory_loc_status"/> <helpers:DataGridColumn Title="MaxQty" Width="80" PropertyName="item_loc_max_qty" /> <helpers:DataGridColumn Title="Type" Width="160" PropertyName="location_type"/> <helpers:DataGridColumn Title="Size" Width="110" PropertyName="location_size"/> </dg:DataGrid.Columns> </dg:DataGrid> </StackLayout>
4. DataGrid的XAML文件代码
<Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <ScrollView Orientation="Horizontal" Grid.Row="0" HorizontalScrollBarVisibility="Never"> <Grid x:Name="HeaderView" RowSpacing="0" Padding="0,0,0,0"> <Grid.Resources> <ResourceDictionary> <!--Default Header Style--> <Style x:Key="HeaderDefaultStyle" TargetType="Label"> <Setter Property="FontSize" Value="12"/> <Setter Property="FontAttributes" Value="Bold"/> <Setter Property="HorizontalOptions" Value="Start"/> <Setter Property="VerticalOptions" Value="Center"/> <Setter Property="HorizontalTextAlignment" Value="Start"/> <Setter Property="LineBreakMode" Value="TailTruncation"/> </Style> <Style TargetType="Grid"> <Setter Property="BackgroundColor" Value="{Binding HeaderBackground,Source={x:Reference Self}}"/> </Style> <Style x:Key="ImageStyleBase" TargetType="Image"> <Setter Property="Aspect" Value="AspectFill"/> <Setter Property="VerticalOptions" Value="Center"/> <Setter Property="HorizontalOptions" Value="Center"/> <Setter Property="HeightRequest" Value="5"/> <Setter Property="WidthRequest" Value="9"/> <!--<Setter Property="Margin" Value="0,0,4,0"/>--> </Style> <Style x:Key="AscendingIconStyle" TargetType="Image" BasedOn="{StaticResource ImageStyleBase}"> <Setter Property="Source" Value="{Binding AscendingIcon, Source={x:Reference Self}}"/> </Style> <Style x:Key="DescendingIconStyle" TargetType="Image" BasedOn="{StaticResource ImageStyleBase}"> <Setter Property="Source" Value="{Binding DescendingIcon, Source={x:Reference Self}}"/> </Style> </ResourceDictionary> </Grid.Resources> </Grid> </ScrollView> <AbsoluteLayout Grid.Row="1"> <ScrollView x:Name="Sv" Orientation="Horizontal" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1"> <helpers:DataGridListView x:Name="ListView" HeightRequest="190" VerticalOptions="Start"></helpers:DataGridListView> </ScrollView> <helpers:DataGridVertScrollBar IsVisible="{Binding EnableCustomScroll}" VerticalOptions="Start" HorizontalOptions="End" x:Name="ssv" HeightRequest="190" WidthRequest="10" AbsoluteLayout.LayoutFlags="HeightProportional,YProportional,XProportional" AbsoluteLayout.LayoutBounds="1,1,8,1"></helpers:DataGridVertScrollBar> </AbsoluteLayout> <ContentView x:Name="NoDataViewElement" Grid.Row="0" Grid.RowSpan="2" VerticalOptions="Center" IsVisible="False"/> </Grid>
排查与解决方案
1. 内存泄漏修复
- 移除手动Dispose操作:
DataTable/DataSet是托管对象,无需手动调用Dispose()和置为null,这会干扰GC正常回收逻辑,直接删除dsSave.Dispose();、dtSave.Dispose();等相关代码。 - 解除事件订阅:检查
SetupListeners()中注册的事件,在页面OnDisappearing方法中取消订阅,避免页面实例被事件引用无法回收。 - 自定义控件资源释放:确保自定义
DataGrid、DataGridListView等控件实现IDisposable接口,在控件销毁时释放绑定的事件、数据源引用等资源。
2. GC与对象创建优化
- 移除强制GC调用:删除
OnAppearing中的GC.Collect()和GC.WaitForPendingFinalizers(),Release模式下GC的自动回收效率更高,手动调用会打乱回收节奏,引发卡顿。 - 减少临时对象创建:
ProcesRecord中Results.ToList()会生成新列表,若Results本身已是List类型,直接使用即可;循环中避免频繁创建DataRow之外的临时对象。
3. 性能监控与定位
- 使用Android Studio Profiler:在Release模式下运行应用,跟踪内存变化,查看持续增长的对象类型和引用链,定位泄漏点。
- 启用MAUI内存诊断:添加MAUI内存快照配置,捕获应用运行时的内存状态,分析未被回收的对象。
4. 页面导航优化
- 清理页面引用:在移除
previousPage后,确认没有其他代码持有该页面的引用;跳转新页面时,避免重复创建相同类型的页面实例,可复用缓存的页面对象。
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

