MonoTouch:应用程序因低内存而被杀,为什么?实时字节分配5 MB顶部

时间:2012-04-29 15:42:14

标签: c# memory-management memory-leaks xamarin.ios memory-warning

我的iPad应用程序是在MonoTouch中开发的,因为我想避免所有的内存管理地狱,但事实并非如此。在模拟器上一切正常,但是当我在设备上测试我的应用程序时,我惊恐地发现,在发出一些内存警告后很快被操作系统杀死了。 我的应用程序是一个简单的图像浏览器,它加载一些PNG图像并使用UIScrollView中的一些UIViews显示它们,在触摸时加载下一个或前一个。在模拟器上它工作正常。但是在加载和卸载大约6-11个图像后的设备上,它开始获得内存警告,然后突然该过程被杀死。我已经检查了所有的实例循环,并在加载新图像之前正确删除了所有引用。

所以我开始使用Instruments并开始在iPad上分析我的应用程序的内存分配。在那里,我发现Live字节只有大约5-9 MB,正如我所期望的那样,但由于一些奇怪的原因,几乎没有收集死内存分配,因为在分配了大约50 MB(小于5-9 MB的它是Live Bytes)它被杀了!以下是我的应用程序的Instruments分析会话的屏幕截图:

Instruments screenshots documenting memory allocation problem under monotouch

这是快照序列:

heapshots of my app in instruments

还有一些小漏洞,但我认为它们不够大,不足以成为罪魁祸首。它们都是来自strdup的48个字节泄漏,这是在iOS 5.1中发布UIScrollView时的一个已知问题:

enter image description here

即使一切看似正常并且分配的Live Bytes仍然是5 MB,我的应用程序的REAL内存呈指数级增长,然后在iPad上被杀死高达50MB,而在iPhone4S上高达314 MB,正如记忆监控:

memory growth of my App on iPhone4S

有人可以告诉我是否有方法或实用工具来发现问题的原因和位置?这是monotouch垃圾收集器的错误吗?或者是否有一些我不能正确处理的物体?我怎么能用探查器找到那些?我已经检查了两天的代码,但一切似乎都正确。

这是我的加载/实例化/处置周期的代码:

    void StartImageLoadingThread()
{
    tokenSource = new CancellationTokenSource ();
    token = tokenSource.Token;
    Task task1 = new Task( () => PerformLoadImageTask(token),token);
    task1.Start();
}


void PerformLoadImageTask(CancellationToken token)
{
        if (token.IsCancellationRequested)
                {
                     Console.WriteLine("Task {0}: Cancelling", Task.CurrentId);
                return;
                }

            current_uiimage_A = UIImage.FromFileUncached(file_name);
            page_A_image_view.Image = current_uiimage_A;


} 


void UnloadImageAFromMemory()
{
      page_A_image_view.Image = null;
      current_uiimage_A.Dispose();
      current_uiimage_A = null;
}   

是否有方法来捕获未正确处理的对象?仪器报告实时字节数低,泄漏几乎为零,那么死亡对象为什么不被丢弃?即使我的应用程序几分钟没有任何反应,GC也似乎无法完成他的工作,即使我在我的代码中称之为明确。我甚至尝试过新的实验垃圾收集器,但是让我的应用程序被杀死的情况更糟,更快。

我在Xamarin网站上找不到任何指南或分步指南,用于排除内存警告或追踪错误分配。

非常感谢任何帮助或建议, 谢谢!

更新: 我已经减少了一些图像和uiviews的大小和分辨率,而Live Bytes现在从9 MB降到5 MB顶部,但是无论如何都会在发出一些内存警告之后该应用仍被杀死。

更新2: 正如Javier所建议的,我已经删除了后台任务并直接进行了调用,内存泄漏也消失了!似乎MonoTouch垃圾收集器有一个错误,并且当UIImages被分配并放置在不同的线程中时无法收集内存。但是现在我的应用程序在滚动时非常无响应,所以我需要找到一个解决方案来在不同的线程中执行它!但是如何?

3 个答案:

答案 0 :(得分:3)

您需要在图像创建代码周围使用NSAutoreleasePools,如下所示:

void PerformLoadImageTask(CancellationToken token)
{
    if (token.IsCancellationRequested)
    {
         Console.WriteLine("Task {0}: Cancelling", Task.CurrentId);
         return;
    }

    using (var pool = new NSAutoreleasePool ()) {
        current_uiimage_A = UIImage.FromFileUncached(file_name);
        page_A_image_view.Image = current_uiimage_A;
    }
}

对于某些创建对象的API,该对象将被添加到自动释放池中,并且在池耗尽之前不会释放。 MonoTouch会自动为所有用户线程(包括线程池,TPL和正常的System.Threading线程)创建自动释放池,但是在线程退出之前不会耗尽这些池(在线程池和TPL线程的情况下无法控制)。所以解决方案是在关键代码周围使用NSAutoreleasePool(在处理时池会自动耗尽)。

答案 1 :(得分:2)

我遇到了UIImage.FromFile的问题。我的应用程序正在使用任务加载大量png图像,并在主线程中显示它。

我在后台任务中添加了GC.Collect,但它没有解决问题。我不得不删除后台任务,在主线程中执行所有操作并调用GC.Collect。似乎Image.Dispose没有释放图像内存:(

但是当你有另一个任务时它不起作用,所以我不得不删除它:(

是的,它不起作用....

答案 2 :(得分:1)

这对我来说很有意思,我刚开始看内存管理,我甚至不确定是否必须对null和对象进行调用处理,因为范围规则应该适合你吗?但是它会帮助垃圾收集器尽快完成。

明确要求垃圾收集器运行是一个请求,而不是保证。

我会猜测在ui线程上创建的对象在ui线程外部被引用并且会在那里查看,但我不确定。

我期待看到更新,因为我确信我会遇到类似的问题!

祝你好运, 罗布