MAUI中如何重复使用Camera.Maui获取的ImageSource?
问题根源分析
Camera.Maui的GetSnapShot()返回的ImageSource基于一次性可读流实现:当流被首次渲染(比如PreviewPage的ImageButton显示)后,流指针会走到末尾并被自动关闭,后续再尝试读取(比如ImagePage中设置Image.Source)就会触发“流已关闭”错误,直接保存这类流也会失败。
解决方案
核心思路是将相机返回的临时流内容持久化到内存(byte[])或本地临时文件,实现多次安全访问,避免流依赖问题。
方案1:转为byte[]存储(适合中小尺寸图片)
修改快照存储逻辑,将一次性流转为可重复使用的byte[]数组:
MainPage.xaml.cs 修改
using Camera.MAUI; using System.IO; private readonly CameraView camera; private readonly List<byte[]> picBytesList = new(); // 替换原List<ImageSource> public MainPage() { InitializeComponent(); camera = cameraView; } public async void GrabSnapshot() { var snapshotSource = camera.GetSnapShot(); if (snapshotSource is StreamImageSource streamSource) { using var stream = await streamSource.Stream(CancellationToken.None); using var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream); picBytesList.Add(memoryStream.ToArray()); } }
PreviewPage.xaml.cs 修改
private void DisplayPics(List<byte[]> picBytesList) { int counter = 0; // 假设已完成GridLayout的行列定义 foreach (var bytes in picBytesList) { int row = (int)Math.Floor(counter / 2m); int column = counter % 2; // 从byte[]生成可重复使用的ImageSource var thumbSource = ImageSource.FromStream(() => new MemoryStream(bytes)); var button = new ImageButton { Source = thumbSource, HeightRequest = 150, // 固定缩略图尺寸减少内存占用 WidthRequest = 200 }; // 绑定跳转事件 button.Clicked += (s, e) => { Navigation.PushAsync(new ImagePage(counter, picBytesList)); }; GridLayout.Add(button, column, row); counter++; } }
ImagePage.xaml.cs 修改
private readonly List<byte[]> _picBytesList; private int _currentIndex; public ImagePage(int indexParam, List<byte[]> picBytesList) { InitializeComponent(); _picBytesList = picBytesList; _currentIndex = indexParam; LoadCurrentImage(); // 添加左右切换逻辑(示例) AddNavigationButtons(); } private void LoadCurrentImage() { var bytes = _picBytesList[_currentIndex]; ImagePanel.Source = ImageSource.FromStream(() => new MemoryStream(bytes)); } private void AddNavigationButtons() { var prevBtn = new Button { Text = "←", HorizontalOptions = LayoutOptions.Start }; prevBtn.Clicked += (s, e) => { if (_currentIndex > 0) { _currentIndex--; LoadCurrentImage(); } }; var nextBtn = new Button { Text = "→", HorizontalOptions = LayoutOptions.End }; nextBtn.Clicked += (s, e) => { if (_currentIndex < _picBytesList.Count - 1) { _currentIndex++; LoadCurrentImage(); } }; // 将按钮添加到页面布局(需根据你的XAML结构调整) var buttonLayout = new HorizontalStackLayout { HorizontalOptions = LayoutOptions.Center }; buttonLayout.Add(prevBtn); buttonLayout.Add(nextBtn); Content = new VerticalStackLayout { Children = { ImagePanel, buttonLayout } }; }
方案2:保存到本地临时文件(适合大尺寸图片,优化内存)
针对4160x3120的JPG,20-50张的byte[]会占用较多内存,可选择保存到APP临时目录,用文件路径加载图片:
MainPage.xaml.cs 修改
using Camera.MAUI; using System.IO; using Microsoft.Maui.Storage; private readonly CameraView camera; private readonly List<string> tempFilePaths = new(); public async void GrabSnapshot() { var snapshotSource = camera.GetSnapShot(); if (snapshotSource is StreamImageSource streamSource) { using var stream = await streamSource.Stream(CancellationToken.None); // 创建临时文件 var tempDir = FileSystem.CacheDirectory; var tempFileName = $"snapshot_{Guid.NewGuid()}.jpg"; var tempPath = Path.Combine(tempDir, tempFileName); using var fileStream = File.OpenWrite(tempPath); await stream.CopyToAsync(fileStream); tempFilePaths.Add(tempPath); } }
临时文件清理
在用户确认保存或APP退出时清理临时文件:
private void CleanupTempFiles() { foreach (var path in tempFilePaths) { if (File.Exists(path)) File.Delete(path); } tempFilePaths.Clear(); }
内存优化建议
- 指定JPG格式:检查Camera.Maui配置,将快照格式设为JPG(替代默认PNG),大幅降低文件体积。
- 生成缩略图:在PreviewPage中对原图进行缩放处理,生成小尺寸缩略图,减少UI渲染内存占用:
public byte[] ResizeImage(byte[] imageBytes, int targetWidth, int targetHeight) { using var stream = new MemoryStream(imageBytes); using var originalImage = PlatformImage.FromStream(stream); using var resizedImage = originalImage.Resize(targetWidth, targetHeight, ResizeMode.AspectFill); using var resizedStream = new MemoryStream(); resizedImage.Save(resizedStream, ImageFormat.Jpeg); return resizedStream.ToArray(); } - 页面销毁时释放资源:在PreviewPage和ImagePage的
OnDisappearing方法中清空Image控件Source,避免内存泄漏:protected override void OnDisappearing() { base.OnDisappearing(); foreach (var child in GridLayout.Children) { if (child is ImageButton btn) btn.Source = null; } ImagePanel.Source = null; }
内容的提问来源于stack exchange,提问作者computercarguy
相关产品推荐
相关产品推荐

