Xamarin.Forms移除再添加Image控件后丢失或抛异常如何解决?
问题描述
我创建了一个简单的Xamarin.Forms示例项目,其中添加了Image控件,通过ImageSource.FromStream()方法以流形式加载图片资源,再将该控件从父布局移除后重新添加时出现异常,各平台表现如下:
- Xamarin.Forms UWP:图片直接消失
- Xamarin.Forms Android:抛出无法访问已关闭的流异常
- Xamarin.Forms iOS:暂未测试
相关代码
XAML:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="ImageException.MainPage"> <ContentPage.Content> <Grid x:Name="MainGrid"> <Image x:Name="image" /> <Button Text="Click_Me" Clicked="Button_Clicked" VerticalOptions="End" /> </Grid> </ContentPage.Content> </ContentPage>
C#:
namespace ImageException { public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); var assembly = typeof(MainPage).Assembly; string path = "ImageException"; var stream = assembly.GetManifestResourceStream($"{path}.Image.Image.png"); image.Source = ImageSource.FromStream(() => stream); } private void Button_Clicked(object sender, EventArgs e) { MainGrid.Children.Remove(image); MainGrid.Children.Add(image); } } }
请问如何实现Image控件从布局移除再添加后仍然可以正常显示?
解决方案
根因说明
异常的核心原因是Stream对象为单次消费资源:你提前创建了唯一的流实例传给ImageSource.FromStream(),首次加载图片时流会被读取,使用完成后会被Xamarin框架自动关闭释放。当你把Image控件从布局移除再重新添加时,控件会尝试重新从绑定的ImageSource中读取流数据,此时流已经处于关闭状态,就会出现对应平台的异常表现。
可选用的修复方案
方案1:委托内每次返回新的流(改动手最少)
不要提前创建流实例,在FromStream的委托中每次调用时都重新获取嵌入资源流,保证每次需要加载图片时都有可用的新流:
public MainPage() { InitializeComponent(); var assembly = typeof(MainPage).Assembly; string path = "ImageException"; // 流实例放在委托内生成,每次调用都会返回新的可用流 image.Source = ImageSource.FromStream(() => { return assembly.GetManifestResourceStream($"{path}.Image.Image.png"); }); }
方案2:缓存字节数组减少重复读资源(性能更优)
如果担心多次读取嵌入资源影响性能,可以先把流读成字节数组缓存起来,每次返回包装了字节数组的新MemoryStream即可:
private byte[] _imageBuffer; public MainPage() { InitializeComponent(); var assembly = typeof(MainPage).Assembly; string path = "ImageException"; // 提前把资源读成字节数组缓存,仅读取一次资源 using var stream = assembly.GetManifestResourceStream($"{path}.Image.Image.png"); using var ms = new MemoryStream(); stream.CopyTo(ms); _imageBuffer = ms.ToArray(); image.Source = ImageSource.FromStream(() => new MemoryStream(_imageBuffer)); }
方案3:直接使用嵌入资源专用加载API
对于嵌入资源的场景,你可以直接用ImageSource.FromResource()方法,不需要自己处理流的生命周期,Xamarin.Forms内部已经做好了流的管理逻辑,代码最简洁:
public MainPage() { InitializeComponent(); image.Source = ImageSource.FromResource("ImageException.Image.Image.png", typeof(MainPage).Assembly); }
以上三种方案都可以保证Image控件移除再重新添加后正常加载图片。
内容的提问来源于stack exchange,提问作者Vignesh Ramesh
相关产品推荐
相关产品推荐

