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

.NET MAUI漫画App在ListView加载大图片崩溃求优化方案

解决.NET MAUI Android加载大图片时的Canvas过大Bitmap崩溃问题

问题根源

你的问题本质是Android系统对单张Bitmap的内存占用有阈值限制,当高分辨率图片(如你提到的24.jpg)在滚动视图(ListView/ScrollView/CollectionView)中加载时,布局测量逻辑可能会强制系统尝试按原始尺寸解析Bitmap,导致内存占用超过100MB阈值,触发Java.Lang.RuntimeException。而添加Margin或嵌套StackLayout能规避问题,是因为这些操作改变了布局测量的计算方式,避免了全尺寸Bitmap的临时加载。

优化解决方案

1. 自定义Image控件,按需缩放图片

在Android平台实现自定义Image Handler,加载图片时根据设备屏幕宽度自动计算缩放比例,降低Bitmap的内存占用:

步骤1:创建共享项目的CustomImage控件

public class CustomImage : Image
{
}

步骤2:在Android项目中实现CustomImageHandler

[assembly: ExportHandler(typeof(CustomImage), typeof(CustomImageHandler))]
namespace YourAppName.Platforms.Android
{
    public class CustomImageHandler : ImageHandler
    {
        protected override async void LoadImageAsync(ImageSource source, CancellationToken cancellationToken = default)
        {
            if (source is UriImageSource uriSource)
            {
                using var httpClient = new HttpClient();
                var stream = await httpClient.GetStreamAsync(uriSource.Uri);
                
                // 计算采样率,按屏幕宽度缩放
                var options = new BitmapFactory.Options();
                options.InJustDecodeBounds = true;
                BitmapFactory.DecodeStream(stream, null, options);
                
                var screenWidth = (int)Context.Resources.DisplayMetrics.WidthPixels;
                options.InSampleSize = CalculateInSampleSize(options, screenWidth, int.MaxValue);
                options.InJustDecodeBounds = false;
                
                // 重置流位置
                stream.Seek(0, SeekOrigin.Begin);
                var bitmap = BitmapFactory.DecodeStream(stream, null, options);
                
                SetNativeImage(bitmap);
                return;
            }
            
            base.LoadImageAsync(source, cancellationToken);
        }
        
        private int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
        {
            var height = options.OutHeight;
            var width = options.OutWidth;
            var inSampleSize = 1;

            if (height > reqHeight || width > reqWidth)
            {
                var halfHeight = height / 2;
                var halfWidth = width / 2;

                while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth)
                {
                    inSampleSize *= 2;
                }
            }

            return inSampleSize;
        }
    }
}

步骤3:替换XAML中的Image为CustomImage

<local:CustomImage x:Name="img" Source="{Binding}" />

2. 替换ListView为CollectionView并优化布局

CollectionView的布局测量逻辑更高效,结合ItemSizingStrategy优化可减少不必要的Bitmap加载:

<CollectionView
    x:Name="collectionView"
    ItemsSource="{Binding ImageUrls}"
    SelectionMode="None">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Image x:Name="img" Source="{Binding}" />
                <Label
                    HeightRequest="600"
                    HorizontalOptions="Center"
                    HorizontalTextAlignment="Center"
                    IsVisible="{Binding IsLoading, Source={x:Reference img}}"
                    Text="loading..."
                    TextColor="Black"
                    VerticalTextAlignment="Center" />
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
    <CollectionView.ItemsLayout>
        <VerticalLinearItemsLayout ItemSizingStrategy="MeasureFirstItem" />
    </CollectionView.ItemsLayout>
</CollectionView>

3. 使用FFImageLoading库(推荐)

FFImageLoading是专为.NET MAUI优化的图片加载库,自带自动缩放、内存缓存和磁盘缓存,能彻底解决大图片内存问题:

安装NuGet包

Install-Package FFImageLoading.Maui

在XAML中使用CachedImage

<ffimageloading:CachedImage
    Source="{Binding}"
    LoadingPlaceholder="loading..."
    DownsampleToViewSize="True"
    DownsampleWidth="1080" />

DownsampleToViewSize="True"会自动按控件尺寸缩放图片,避免加载原始大尺寸Bitmap。

4. 局部关闭硬件加速而非全局

不要全局关闭硬件加速,仅对问题视图关闭,保留其他界面的流畅度:

在Android项目的MainActivity中,找到ListView对应的原生View并设置:

protected override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);
    
    // 假设你的ListView的x:Name是listView
    var nativeListView = listView.Handler.PlatformView as Android.Widget.ListView;
    nativeListView?.SetLayerType(LayerType.Software, null);
}

验证方案

优先尝试方案3(FFImageLoading)或方案1,测试你提到的24.jpg是否还会触发崩溃,同时检查滑动流畅度是否符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 03:44:52