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

Xamarin Android滚动图片ListView时触发System.ObjectDisposedException

问题描述

使用Xamarin.Forms开发Android应用时,包含图片列表的页面抛出 System.ObjectDisposedException: 'Cannot access a closed Stream.' 异常,触发规律为:向下滚动ListView时图片加载正常,向上回滚列表时触发异常。
页面所用图片以byte[]格式存储在数据库中,当前实现通过MemoryStream为Image控件的Source属性赋值,已确认异常由流被关闭导致,无法找到维持流打开状态或规避问题的可行方案。

原实现代码

页面XAML代码

<ListView ItemsSource="{Binding imageGallery}" x:Name="imageList" RowHeight="100" VerticalScrollBarVisibility="Always">
    <ListView.ItemTemplate >
        <DataTemplate>
            <ViewCell>
                <Grid >
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="0.5*" />
                        <ColumnDefinition Width="0.5*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="100"/>
                    </Grid.RowDefinitions>
                    <Image Source="{Binding photo.Source}" x:Name="imageInGrid" Rotation="90" Grid.Column="0" MinimumHeightRequest="100" VerticalOptions="FillAndExpand"/>
                    <StackLayout Orientation="Vertical" Grid.Column="1" VerticalOptions="Center" >
                        <Label Text="{Binding date}" FontSize="18" TextColor="Navy" HorizontalTextAlignment="Start" VerticalTextAlignment="Center" />
                        <Label Text="{Binding manifestNum}" FontSize="18" TextColor="Navy" HorizontalTextAlignment="Start" VerticalTextAlignment="Center" FontAttributes="Bold" />
                    </StackLayout>
                </Grid>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

页面C#代码

static ObservableCollection<ImageViewer> imageGallery;
static List<Stream> streams;
public ImagesPage()
{
    InitializeComponent();
    imageGallery = new ObservableCollection<ImageViewer>();
    streams = new List<Stream>();

    //query string
    using (SqlConnection conn = new SqlConnection(queryString))
    {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand(query, conn))
        {
            using (SqlDataReader reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    ImageViewer viewer = new ImageViewer
                    {
                        imageData = (byte[]) reader[2],
                        photo = new Image(),
                        date = reader[1].ToString().Split(' ')[0],
                        manifestNum = reader[0].ToString().Substring(4)
                    };
                    Stream stream = new MemoryStream(viewer.imageData);
                    viewer.photo.Source = ImageSource.FromStream(() => { return stream; });
                    streams.Add(stream);
                    imageGallery.Add(viewer);
                }
            }
        }
    }
    imageList.ItemsSource = imageGallery;
}
问题根因
  • ListView自带视图回收复用机制:滚动时离屏的列表项会被缓存,回滚到对应位置时会重新触发图片加载流程,调用ImageSource.FromStream传入的工厂委托获取流
  • 原代码只在列表初始化时为每张图创建了一次MemoryStream实例,图片首次加载完成后,Xamarin.Forms的平台渲染层会自动关闭并释放这个流。回滚复用时再次访问已释放的流,就会抛出ObjectDisposedException
  • 原代码存在冗余设计:在数据模型层提前创建Image UI控件完全没有必要,DataTemplate会自动生成对应UI元素,提前创建只会额外占用内存
  • 把集合、流列表设为static会导致页面销毁后资源无法被GC回收,引发内存泄漏
修复方案

核心修复逻辑:不要缓存复用同一个MemoryStream实例,每次FromStream的工厂委托被调用时,基于存储的byte[]新建MemoryStream即可。平台加载完图片后会自动释放这个新建的流,下次回滚复用时重新生成新流,就不会出现访问已释放流的问题。

修正后C#代码

// 移除static修饰和无意义的流缓存列表,避免内存泄漏
ObservableCollection<ImageViewer> imageGallery;
public ImagesPage()
{
    InitializeComponent();
    imageGallery = new ObservableCollection<ImageViewer>();

    using (SqlConnection conn = new SqlConnection(queryString))
    {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand(query, conn))
        {
            using (SqlDataReader reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    byte[] imgBytes = (byte[])reader[2];
                    ImageViewer viewer = new ImageViewer
                    {
                        imageData = imgBytes,
                        date = reader[1].ToString().Split(' ')[0],
                        manifestNum = reader[0].ToString().Substring(4),
                        // 关键:每次委托调用都新建MemoryStream,不复用旧流
                        PhotoSource = ImageSource.FromStream(() => new MemoryStream(imgBytes))
                    };
                    imageGallery.Add(viewer);
                }
            }
        }
    }
    imageList.ItemsSource = imageGallery;
}

// 修正数据模型,移除冗余的UI控件字段,只保留绑定需要的数据
public class ImageViewer
{
    public byte[] imageData { get; set; }
    public string date { get; set; }
    public string manifestNum { get; set; }
    public ImageSource PhotoSource { get; set; }
}

修正后XAML代码

<!-- 可添加CachingStrategy="RecycleElement"进一步优化列表滚动性能 -->
<ListView ItemsSource="{Binding imageGallery}" x:Name="imageList" RowHeight="100" VerticalScrollBarVisibility="Always" CachingStrategy="RecycleElement">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <Grid >
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="0.5*" />
                        <ColumnDefinition Width="0.5*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="100"/>
                    </Grid.RowDefinitions>
                    <!-- 绑定路径改为数据模型中的PhotoSource属性 -->
                    <Image Source="{Binding PhotoSource}" x:Name="imageInGrid" Rotation="90" Grid.Column="0" MinimumHeightRequest="100" VerticalOptions="FillAndExpand"/>
                    <StackLayout Orientation="Vertical" Grid.Column="1" VerticalOptions="Center" >
                        <Label Text="{Binding date}" FontSize="18" TextColor="Navy" HorizontalTextAlignment="Start" VerticalTextAlignment="Center" />
                        <Label Text="{Binding manifestNum}" FontSize="18" TextColor="Navy" HorizontalTextAlignment="Start" VerticalTextAlignment="Center" FontAttributes="Bold" />
                    </StackLayout>
                </Grid>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
优化说明
  • 不需要手动维护流的生命周期,每次工厂委托中新建的MemoryStream会在图片加载完成后被平台自动释放,不会造成内存泄漏
  • 开启RecycleElement缓存策略后,列表滚动时只会复用离屏的Cell对象,不需要反复创建销毁UI元素,能大幅降低内存占用、提升滚动流畅度
  • 数据模型层不要存放UI控件实例,遵循数据和UI分离的原则,避免跨线程访问UI、内存泄漏等问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 13:45:36