如何确保Xamarin.Forms中ImageSource不被GC释放?
升级Xamarin.Forms所有依赖以准备迁移至MAUI时,发现信使模块的GetConversations函数获取的对话列表加载到ListView时,List<KeyValuePair<Conversation, ImageSource>>中的ImageSource对应的流被GC回收,抛出System.ObjectDisposedException(无法访问已关闭的流),最终导致应用崩溃。旧版本Xamarin.Forms和.NET中无此问题,推测是新版GC效率提升提前回收了流对象。
相关代码
GetConversations函数
public static async void GetConversations() { Console.WriteLine("ran!"); IQuerySnapshot conversations = await App.Firestore.Collection("conversations").WhereEqualsTo("Members." + App.Authentication.CurrentUser.Uid, true).GetAsync(); foreach (IDocumentSnapshot doc in conversations.Documents) { Conversation convo = doc.ToObject<Conversation>(); convo.GetLatests(doc.Id); string uid = convo.Members.Keys.Where(x => !x.Contains(App.Authentication.CurrentUser.Uid)).First(); ImageSource profile = await ImageHandle.GetProfileImage(uid); ConversationsList.Add(new KeyValuePair<Conversation, ImageSource>(convo, profile)); } ConversationsReady?.Invoke(ConversationsList, new EventArgs()); }
ImageHandle.GetProfileImage函数
public static async Task<ImageSource> GetProfileImage(string userID) { try { System.IO.Stream imageStream = await App.FirebaseStorage.GetInstanceFromUrl("gs://av_profile_images").RootReference.Child(userID + "_1080x1080.jpeg").GetStreamAsync(); return ImageSource.FromStream(() => imageStream); } catch (FirebaseStorageException e) { Console.WriteLine("Failed to get profile image!"); App.Crashlytics.LogError(e, "Failed to get profile image"); return ImageSource.FromFile("ProfilePlaceholder"); } }
异常信息
[OpenGLRenderer] --- Failed to create image decoder with message 'unimplemented'
Resolved pending breakpoint at 'ImageHandle.cs:53,1' to void AV.Resources.Extensions.ImageHandle.d__2.MoveNext () [0x0011f].
[istsandvenue.AV] Attempt to remove index outside index area (0 vs 1-1)
[istsandvenue.AV] JNI WARNING: DeleteLocalRef(0x19) failed to find entry
[istsandvenue.AV] at mono.android.runtime.InputStreamAdapter.n_read(Native method)
[istsandvenue.AV] at mono.android.runtime.InputStreamAdapter.read(InputStreamAdapter.java:57)
[istsandvenue.AV] at android.graphics.BitmapFactory.nativeDecodeStream(Native method)
[istsandvenue.AV] at android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.java:791)
[istsandvenue.AV] at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:766)
[istsandvenue.AV] at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:807)
Image loading: Image load failed: System.ObjectDisposedException: Cannot access a closed Stream.
at System.IO.MemoryStream.EnsureNotClosed () [0x00008] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/Common/src/CoreLib/System/IO/MemoryStream.cs:121
at System.IO.MemoryStream.Read (System.Byte[] buffer, System.Int32 offset, System.Int32 count) [0x0004e] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/Common/src/CoreLib/System/IO/MemoryStream.cs:369
at Android.Runtime.InputStreamAdapter.Read (System.Byte[] bytes, System.Int32 offset, System.Int32 length) [0x00000] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/InputStreamAdapter.cs:38
at Java.IO.InputStream.n_Read_arrayBII (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_b, System.Int32 off, System.Int32 len) [0x0001f] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/monoandroid10/android-33/mcw/Java.IO.InputStream.cs:274
at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLII_I (_JniMarshal_PPLII_I callback, System.IntPtr jnienv, System.IntPtr klazz, System.IntPtr p0, System.Int32 p1, System.Int32 p2) [0x00005] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:264
at (wrapper native-to-managed) Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLII_I(intptr,intptr,intptr,int,int)
at (wrapper managed-to-native) Java.Interop.NativeMethods.java_interop_jnienv_call_static_object_method_a(intptr,intptr&,intptr,intptr,intptr)
at Java.Interop.JniEnvironment+StaticMethods.CallStaticObjectMethod (Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0003f] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/JniEnvironment.g.cs:12886
at Java.Interop.JniPeerMembers+JniStaticMethods.InvokeObjectMethod (System.String encodedMember, Java.Interop.JniArgumentValue* parameters) [0x00008] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs:151
at Android.Graphics.BitmapFactory.DecodeStream (System.IO.Stream is) [0x0001f] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/monoandroid10/android-33/mcw/Android.Graphics.BitmapFactory.cs:835
at Android.Graphics.BitmapFactory+<>c__DisplayClass31_0.b__0 () [0x00000] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/monoandroid10/android-33/mcw/Android.Graphics.BitmapFactory.cs:845
at System.Threading.Tasks.Task`1[TResult].InnerInvoke () [0x0000f] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs:534
at System.Threading.Tasks.Task.Execute () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:2319
--- End of stack trace from previous location where exception was thrown ---at Xamarin.Forms.Platform.Android.StreamImagesourceHandler.LoadImageAsync (Xamarin.Forms.ImageSource imagesource, Android.Content.Context context, System.Threading.CancellationToken cancelationToken) [0x000da] in D:\a\1\s\Xamarin.Forms.Platform.Android\Renderers\StreamImagesourceHandler.cs:19
at Xamarin.Forms.Platform.Android.ResourceManager.GetFormsBitmapAsync (Android.Content.Context context, Xamarin.Forms.ImageSource imageSource, System.Threading.CancellationToken cancellationToken) [0x0006d] in D:\a\1\s\Xamarin.Forms.Platform.Android\ResourceManager.cs:152
ImageLoaderSourceHandler: Image data was invalid: Xamarin.Forms.StreamImageSource
随后应用出现致命信号:
[libc] Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xb83cd000000008 in tid 12022 (istsandvenue.AV), pid 12022 (istsandvenue.AV)
解决方案
问题根源
ImageSource.FromStream(() => imageStream)返回的是延迟执行的委托,ListView真正渲染图片时才会调用该委托获取流,但此时原始imageStream已被GC回收并关闭,导致异常。新版GC效率更高,提前回收了无直接引用的流对象。
修复代码
将流内容复制到MemoryStream中保留数据,并在委托中返回可重置位置的内存流实例:
public static async Task<ImageSource> GetProfileImage(string userID) { try { using (var originalStream = await App.FirebaseStorage.GetInstanceFromUrl("gs://av_profile_images").RootReference.Child(userID + "_1080x1080.jpeg").GetStreamAsync()) { var memoryStream = new MemoryStream(); await originalStream.CopyToAsync(memoryStream); memoryStream.Position = 0; return ImageSource.FromStream(() => { memoryStream.Position = 0; return memoryStream; }); } } catch (FirebaseStorageException e) { Console.WriteLine("Failed to get profile image!"); App.Crashlytics.LogError(e, "Failed to get profile image"); return ImageSource.FromFile("ProfilePlaceholder"); } }
额外优化建议
- 将
GetConversations的返回类型从async void改为async Task,确保异步方法的异常可被捕获:
public static async Task GetConversations() { // 原有逻辑不变 }
- 考虑使用图片缓存库(如FFImageLoading)管理图片资源,减少GC压力,同时提升加载性能,该方案也能更好地兼容MAUI。
内容的提问来源于stack exchange,提问作者Matthew Swallow

