You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 11:17:34