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

Xamarin Essentials MediaPicker CapturePhotoAsync拍照后应用静默重启问题求助

Xamarin Essentials MediaPicker 拍照后静默崩溃修复方案

问题特性

  • 仅在部分设备(典型如小米Redmi Note 9S)上复现,本地测试环境无异常
  • 拍照点击确认后应用直接重启,AppCenter无任何异常日志上报
  • 属于系统级低内存杀进程场景,不会触发应用层的异常捕获逻辑

核心原因

  1. 原生MediaPicker在小米定制ROM上存在已知内存泄漏问题,调用拍照后会占用大量系统RAM
  2. 现有图片处理逻辑存在严重内存浪费:同一份原图流被读取3次,Bitmap对象未主动释放,大内存数组未及时回收,短时间内内存占用峰值超过小米系统的进程内存阈值,被系统强制杀死

修复方案

1. 优化图片处理内存占用

核心优化点

  • 仅读取一次原图流,缓存为字节数组后所有操作复用该数组,避免重复加载大文件
  • 所有Bitmap对象包裹在using块中,用完自动回收资源
  • 大字节数组处理完后主动置空,配合手动触发GC及时释放内存
  • 增加EXIF读取异常兜底,避免元数据解析失败导致崩溃

优化后核心代码

// 优化后的LoadPhotoAsync方法
async Task LoadPhotoAsync(FileResult photo)
{
    if (photo == null)
    {
        PhotoPath = null;
        return;
    }
    // 仅加载一次原图数据
    byte[] originalImageBytes;
    using (var stream = await photo.OpenReadAsync())
    {
        originalImageBytes = ReadFully(stream);
    }

    // 读取EXIF旋转信息
    int rotationAngle = 90;
    try
    {
        using var exifStream = new MemoryStream(originalImageBytes);
        var meta = ImageMetadataReader.ReadMetadata(exifStream);
        var subIfd0Directory = meta.OfType<ExifIfd0Directory>().FirstOrDefault();
        var orientation = subIfd0Directory?.GetDescription(ExifDirectoryBase.TagOrientation);
        rotationAngle = GetRotationAngle(orientation);
    }
    catch (Exception ex)
    {
        var properties = new Dictionary<string, string>
        {
            { "Função:", "LoadPhotoAsync" },
            { "Arquivo:", "VisitDetailsRegistrationViewModel"},
            { "Observação", "EXIF读取异常"}
        };
        Crashes.TrackError(ex, properties);
    }

    // 处理原图保存
    var newFile = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
    var rotatedImageBytes = RotateImage(originalImageBytes, rotationAngle, 92);
    using (var saveStream = new MemoryStream(rotatedImageBytes))
    using (var fileStream = File.OpenWrite(newFile))
    {
        await saveStream.CopyToAsync(fileStream);
    }

    // 保存到图库
    using (var galleryStream = new MemoryStream(rotatedImageBytes))
    {
        try
        {
            await MediaGallery.SaveAsync(MediaFileType.Image, galleryStream, photo.FileName);
        }
        catch (Exception ex)
        {
            var properties = new Dictionary<string, string>
            {
                { "Função:", "LoadPhotoAsync" },
                { "Arquivo:", "VisitDetailsRegistrationViewModel"},
                { "Observação", "保存到图库异常"}
            };
            Crashes.TrackError(ex, properties);
        }
    }
    rotatedImageBytes = null;
    PhotoPath = newFile;

    // 处理缩略图
    var thumbFile = Path.Combine(FileSystem.CacheDirectory, "Thumb-" + photo.FileName);
    var resizedThumbBytes = ResizeImage(originalImageBytes, 500, 500, 92);
    var rotatedThumbBytes = RotateImage(resizedThumbBytes, rotationAngle, 92);
    resizedThumbBytes = null;

    using (var thumbStream = new MemoryStream(rotatedThumbBytes))
    using (var thumbFileStream = File.OpenWrite(thumbFile))
    {
        await thumbStream.CopyToAsync(thumbFileStream);
    }

    // 主动释放所有大内存资源
    rotatedThumbBytes = null;
    originalImageBytes = null;
    GC.Collect();
    ThumbPath = thumbFile;
}

// 优化后的RotateImage方法,自动回收Bitmap资源
public static byte[] RotateImage(byte[] imageData, float degrees, int quality)
{
    using Bitmap originalImage = BitmapFactory.DecodeByteArray(imageData, 0, imageData.Length);
    using var matrix = new Matrix();
    matrix.PostRotate(degrees);
    using Bitmap rotatedBitmap = Bitmap.CreateBitmap(originalImage, 0, 0, originalImage.Width, originalImage.Height, matrix, true);
    using var ms = new MemoryStream();
    rotatedBitmap.Compress(Bitmap.CompressFormat.Jpeg, quality, ms);
    return ms.ToArray();
}

// 优化后的ResizeImage方法,自动回收Bitmap资源
public static byte[] ResizeImage(byte[] imageData, float width, float height, int quality)
{
    using Bitmap originalImage = BitmapFactory.DecodeByteArray(imageData, 0, imageData.Length);
    float scaleFactor = originalImage.Width > originalImage.Height 
        ? width / originalImage.Width 
        : height / originalImage.Height;
    int newWidth = (int)(originalImage.Width * scaleFactor);
    int newHeight = (int)(originalImage.Height * scaleFactor);
    using Bitmap resizedImage = Bitmap.CreateScaledBitmap(originalImage, newWidth, newHeight, false);
    using var ms = new MemoryStream();
    resizedImage.Compress(Bitmap.CompressFormat.Jpeg, quality, ms);
    return ms.ToArray();
}

2. 系统配置适配

在Android项目的AndroidManifest.xml的application标签中添加android:largeHeap="true"配置,允许应用申请更大的内存空间,降低被系统低内存杀死的概率。

3. 组件替换(可选)

如果优化后仍然有复现,可将Xamarin Essentials的MediaPicker替换为第三方Xam.Plugin.Media库,该库对国产定制ROM的兼容性更好,内存占用更低。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 23:36:04