图像工具 动画图像不释放内存



我有一个WindowsPhone 7.8应用程序,它需要显示动画GIF以及Silverlight直接支持的图像。因此,我们创建了一个自定义用户控件public sealed partial class ExtendedImageControl : UserControl, IDisposable,它具有一个名为ImageSource的依赖属性。此属性绑定到 URL。然后,隐藏的代码会将常规 Image 控件插入到 LayoutRoot 或 AnimatedImage 中。但是,当 AnimatedImage 退出视图或包含页面关闭时,它不会释放其内存。

加载逻辑如下:

ExtendedImage loadedImage = (ExtendedImage)await Utils.Caching.Image.LoadCachedImageFromUrlAsync<ExtendedImage>(loadedLocation);
                        if (ImageSource == loadedLocation)
                        {
                            AnimatedImage image = new AnimatedImage();
                            image.Stretch = stretch;
                            image.Source = loadedImage;
                            LayoutRoot.Children.Add(image);
                            CurrentImageMode = ExtendedImageMode.AnimatedImage;
                            loadedImage = null;
#if DEBUG
                            App.logger.log("Loaded {0} as animated image", loadedLocation);
#endif
                            imageDisplay = image;
                            raiseImageUpdated();
                        }

最终,图像加载

        WebClient client = new WebClient();
        ExtendedImage image = new ExtendedImage();
        using (Stream source = await client.OpenReadTaskAsync(location))
        {
            if (location.ToString().EndsWith("gif", StringComparison.InvariantCultureIgnoreCase))
            {
                image.SetSource(source);
                TaskCompletionSource<ExtendedImage> imageLoaded = new TaskCompletionSource<ExtendedImage>();
                EventHandler loadingCompleteHandler = new EventHandler((sender, e) =>
                {
                    imageLoaded.SetResult(image);
                });
                EventHandler<UnhandledExceptionEventArgs> loadingFailedHandler = new EventHandler<UnhandledExceptionEventArgs>((sender, e) =>
                {
                    imageLoaded.SetResult(image);
#if DEBUG
                    if (System.Diagnostics.Debugger.IsAttached)
                        System.Diagnostics.Debugger.Break();
#endif
                });

                image.LoadingCompleted += loadingCompleteHandler;
                image.LoadingFailed += loadingFailedHandler;
                image = await imageLoaded.Task;
                //Remove handlers, otherwise the object might be kept in the memory
                image.LoadingCompleted -= loadingCompleteHandler;
                image.LoadingFailed -= loadingFailedHandler;
            }
            else
            {
                //... load with native Silverlight methods
            }
        }
        return image;

我们很早就注意到了内存问题,因此该控件实现了 IDisposable 接口

    public void Dispose()
    {
        unloadImage();
        GC.SuppressFinalize(this);
    }
    private void unloadImage()
    {
        SmartDispatcher.BeginInvoke(() =>
        {
            if (imageDisplay != null)
            {
                if (imageDisplay is AnimatedImage)
                {
                    if ((imageDisplay as AnimatedImage).Source != null & (imageDisplay as AnimatedImage).Source.Frames != null)
                        (imageDisplay as AnimatedImage).Source.Frames.Clear();
                    (imageDisplay as AnimatedImage).Stop();
                    (imageDisplay as AnimatedImage).Source = null;
                }
                else if (imageDisplay is Image && ((Image)imageDisplay).Source != GIFplaceholder)
                {
                    (imageDisplay as Image).Source = null;
                }
                imageDisplay = null;
            }
        });
    }

但是,永远不会在图像上调用 Dispose 方法。

我该怎么做才能找出为什么 GC 没有拾取此对象?我没有注册任何事件处理程序,因此据我了解,应该在导航离开应用程序页面时收集它。我也尝试添加一个析构函数,但是这个也没有被调用。

内存泄漏不在 ImageTools 中,但实际上是 Silverlight 运行时中的一个错误:

动态添加和删除图像时内存泄漏

解决办法:在动态添加或移除位图图像时 应用程序(又名从树中添加/删除),您应该设置 Image.Source = null,然后从树中删除 Image 元素。 这将使位图图像符合垃圾回收的条件。错误 状态:活动错误。*

引用:

Silverlight:如何从内存中卸载(处置)图像?https://blogs.msdn.microsoft.com/silverlight_sdk/2008/10/28/silverlight-bugs-and-workarounds/

可以快速验证在向后导航离开 XAML 页面之前将每个扩展映像设置为"无"是否可以显著减少应用程序内存使用量。

我将以下内容添加到 App.xaml 中的RootFrame_Navigating事件中,以帮助跟踪这些泄漏:

Dim applicationCurrentMemoryUsage As Long = Microsoft.Phone.Info.DeviceExtendedProperties.GetValue("ApplicationCurrentMemoryUsage")
Dim applicationPeakMemoryUsage As Long = Microsoft.Phone.Info.DeviceExtendedProperties.GetValue("ApplicationPeakMemoryUsage")
Debug.WriteLine(DateTime.Now.ToLongTimeString() & " Current : " & applicationCurrentMemoryUsage & "  Peak : " & applicationPeakMemoryUsage)

当应用程序页面导航离开时,应收集它

将代码放入导航过程中以实际调用 dispose 并取消图像与页面的链接。保存图像的任何内容可能不会被处理,因此图像永远不会被处置。手动完成,然后查看是否发生相同的情况。


OP报告说,关于动画图像的一些事情仍然是一个问题。确保没有将映像pinning到内存的订阅。必须通过-=取消订阅流程删除所有订阅。否则,即使没有对对象的引用,任何订阅也会将其固定到内存中,使其保持活动状态。

如果有人偶然发现这个...

有关解决方案,请参阅此处。

简短回答:在导航离开之前调用 AnimatedImage 上的 Stop()。

编辑:我已经更新了库,以便在从页面中删除控件时自动停止动画。下载并构建最新版本的源代码。

最新更新