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

MAUI中CarouselView二次填充数据时Android端崩溃问题

问题描述

应用首次加载时,CarouselView在iOS和Android端均能正常展示数据;但通过WeakReferenceMessenger触发数据重载(如切换新闻国家、分类或清除过滤器)时,iOS端正常,Android端会在LoadNewsAsync方法的CarouselNewsList.Add(nw)代码处崩溃,无明确报错信息,怀疑为UI线程相关问题。

XAML代码

<CarouselView x:Name="newsCarousel"
                          Position="{Binding CurrentCarouselIndex, Mode=TwoWay}"
                          Loop="False"
                          IsVisible="{Binding IsLoading, Converter={StaticResource boolToBoolInvertConverter}}"
                          ItemsSource="{Binding CarouselNewsList}"
                          Grid.Row="0"
                          IndicatorView="indicatorview">
                <CarouselView.ItemTemplate>
                    <DataTemplate x:DataType="models:NewsModel">
                        <Border HeightRequest="150"
                                Padding="0"
                                Margin="0"
                                StrokeShape="RoundRectangle 10"
                                Stroke="Transparent">

                            <Grid>
                                <Image   Source="{Binding NewsImageUrl, Converter={StaticResource imageResizerConverter}}"
                                         Aspect="AspectFill"/>


                                <Grid Background="{StaticResource GradientBlack}">


                                    <StackLayout VerticalOptions="End"
                                                 Spacing="5"
                                                 Padding="20,0,0,20">
                                        <Label Text="{Binding NewsName}"
                                               Grid.Row="1"
                                               TextColor="{StaticResource White}"
                                               FontSize="{StaticResource MiddleSize}"/>
                                        <Label Text="{Binding NewsProvider}"
                                               Grid.Row="2"
                                               TextColor="{StaticResource White}"
                                               FontSize="{StaticResource SmallSize}"/>
                                    </StackLayout>

                                </Grid>

                            </Grid>

                            <Border.GestureRecognizers>
                                <TapGestureRecognizer Tapped="OnNewsItemTapped"/>
                            </Border.GestureRecognizers>
                        </Border>
                    </DataTemplate>
                </CarouselView.ItemTemplate>
            </CarouselView>
            <IndicatorView  x:Name="indicatorview"
                            IsVisible="{Binding IsLoading, Converter={StaticResource boolToBoolInvertConverter}}"
                            HorizontalOptions="End"
                            IndicatorColor="White"
                            IndicatorsShape="Circle"
                            MaximumVisible="6"
                            IndicatorSize="4"
                            Margin="20"
                            VerticalOptions="EndAndExpand"
                            SelectedIndicatorColor="#0F9D58"/>

ViewModel核心代码

public partial class NewsViewModel : ObservableObject
{
    public ObservableCollection<NewsModel> CarouselNewsList { get; set; }
    private int NumberAddedNews = 0;

    private async Task LoadNewsAsync()
    {
        IsLoading = true;
        try
        {
            await GetLocalDB();
            UserNewsLanguageCode = MyMvvMuser.NewsLanguage;
            _currentOffset = 0;
            var MyNewsData = await NewsDataAPIManager.LoadNewsDataAsync(UserNewsLanguageCode, CategoryToFilter, _currentOffset, _limit);
            IsNewsExist = MyFunctionShared.IsListNotNullOrEmpty(MyNewsData);

            await MainThread.InvokeOnMainThreadAsync(() =>
            {
                NewsList.Clear();
                CarouselNewsList.Clear();
                if (IsNewsExist)
                {
                    foreach (var nw in MyNewsData)
                    {
                        if (NumberAddedNews <= 3)
                        {
                            CarouselNewsList.Add(nw); // Android端崩溃位置
                        }
                        else
                        {
                            NewsList.Add(nw);
                        }
                        NumberAddedNews++;
                    }
                    _currentOffset += _limit;
                }
            });
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error loading cards: {ex.Message}");
        }
        finally
        {
            IsLoading = false;
        }
    }

    // 消息订阅触发重载
    WeakReferenceMessenger.Default.Register<NewsCountrySelectedMessage>(this, async (r, m) =>
    {
        UserNewsLanguageCode = m.Value.Code;
        NumberAddedNews = 0;
        CategoryToFilter = string.Empty;
        MyMvvMuser.NewsLanguage = UserNewsLanguageCode;
        LoadNewsCommand.Execute(null);
    });
}

页面代码核心部分

public partial class MyNewsPage : ContentPage
{
    private NewsViewModel _viewModel;
    [Obsolete]
    public MyNewsPage()
    {
        InitializeComponent();
        _viewModel = new NewsViewModel(App.UserDatabase);
        BindingContext = _viewModel;
    }
}

问题分析与修复方案

核心原因

  1. 多线程竞争状态:NumberAddedNews是ViewModel的成员变量,消息订阅回调中重置该变量后,若前一次LoadNewsAsync未执行完成,会导致多线程并发修改该变量,进而在UI线程添加数据时触发异常。
  2. Android集合渲染严格性:Android对ObservableCollection的UI线程操作检查更苛刻,直接Clear后连续Add可能与CarouselView的渲染逻辑冲突。
  3. 异步命令未等待:消息订阅中直接调用LoadNewsCommand.Execute(null),若为异步命令未等待前一次任务完成,会引发集合并发修改。

具体修复步骤

1. 隔离批次计数,避免多线程冲突

将全局的NumberAddedNews替换为方法内的局部变量,确保每次加载任务的计数独立:

private async Task LoadNewsAsync()
{
    IsLoading = true;
    try
    {
        await GetLocalDB();
        UserNewsLanguageCode = MyMvvMuser.NewsLanguage;
        _currentOffset = 0;
        var MyNewsData = await NewsDataAPIManager.LoadNewsDataAsync(UserNewsLanguageCode, CategoryToFilter, _currentOffset, _limit);
        IsNewsExist = MyFunctionShared.IsListNotNullOrEmpty(MyNewsData);

        // 用局部变量隔离批次计数,避免多线程共享冲突
        int batchAddedCount = 0;

        await MainThread.InvokeOnMainThreadAsync(() =>
        {
            NewsList.Clear();
            CarouselNewsList.Clear();
            if (IsNewsExist)
            {
                foreach (var nw in MyNewsData)
                {
                    if (batchAddedCount <= 3)
                    {
                        CarouselNewsList.Add(nw);
                    }
                    else
                    {
                        NewsList.Add(nw);
                    }
                    batchAddedCount++;
                }
                _currentOffset += _limit;
            }
        });
    }
    catch (Exception ex)
    {
        // 增加详细日志排查异常
        Console.WriteLine($"Error loading cards: {ex.Message}");
        Console.WriteLine($"Inner exception: {ex.InnerException?.Message}");
        Console.WriteLine($"Stack trace: {ex.StackTrace}");
    }
    finally
    {
        IsLoading = false;
    }
}

2. 异步命令等待执行

修改消息订阅逻辑,确保异步命令执行完成后再处理下一次请求:

WeakReferenceMessenger.Default.Register<NewsCountrySelectedMessage>(this, async (r, m) =>
{
    UserNewsLanguageCode = m.Value.Code;
    CategoryToFilter = string.Empty;
    MyMvvMuser.NewsLanguage = UserNewsLanguageCode;
    
    // 若使用CommunityToolkit.Mvvm的IAsyncRelayCommand,直接等待执行
    if (LoadNewsCommand is IAsyncRelayCommand asyncCmd)
    {
        await asyncCmd.ExecuteAsync(null);
    }
    else
    {
        LoadNewsCommand.Execute(null);
    }
});

3. 优化集合更新方式(可选)

通过创建新集合替换原集合,减少UI渲染冲突:

await MainThread.InvokeOnMainThreadAsync(() =>
{
    var newCarouselList = new ObservableCollection<NewsModel>();
    var newNewsList = new ObservableCollection<NewsModel>();

    if (IsNewsExist)
    {
        int count = 0;
        foreach (var nw in MyNewsData)
        {
            if (count <=3)
            {
                newCarouselList.Add(nw);
            }
            else
            {
                newNewsList.Add(nw);
            }
            count++;
        }
        _currentOffset += _limit;
    }

    // 替换集合并通知属性变更
    CarouselNewsList = newCarouselList;
    NewsList = newNewsList;
    OnPropertyChanged(nameof(CarouselNewsList));
    OnPropertyChanged(nameof(NewsList));
});

内容的提问来源于stack exchange,提问作者hugo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:35:54