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

如何确保Xamarin.Forms中ImageSource不被GC释放?

问题:升级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");
    }
}

额外优化建议

  1. 将GetConversations的返回类型从async void改为async Task,确保异步方法的异常可被捕获:
public static async Task GetConversations()
{
    // 原有逻辑不变
}
  1. 考虑使用图片缓存库(如FFImageLoading)管理图片资源,减少GC压力,同时提升加载性能,该方案也能更好地兼容MAUI。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 08:05:56