【问题标题】:Is this a memory leak in Xamarin Forms?这是 Xamarin Forms 中的内存泄漏吗?
【发布时间】:2014-10-01 15:41:34
【问题描述】:

我遇到了一个问题,页面对象一旦被导航离开,就不会被垃圾收集。我已经整理了一个非常基本的示例,演示了使用 NavigationPage 和 PushAsync 方法时的问题。该页面使用弱引用列表显示“活动”页面的数量:

public class AppNavigationPage
{
    private static List<WeakReference> pageRefs = new List<WeakReference>();

    public static Page GetMainPage()
    {
        return new NavigationPage(CreateWeakReferencedPage());
    }

    private static Page CreateWeakReferencedPage()
    {
        GC.Collect();
        var result = CreatePage();
        pageRefs.Add(new WeakReference(result));

        // Add a second unreferenced page to prove that the problem only exists
        // when pages are actually navigated to/from
        pageRefs.Add(new WeakReference(CreatePage()));
        GC.Collect();
        return result;
    }

    private static Page CreatePage()
    {
        var page = new ContentPage();
        var contents = new StackLayout();

        contents.Children.Add(
            new Button
            {
                Text = "Next Page",
                Command = new Command(() => page.Navigation.PushAsync(CreateWeakReferencedPage()))
            });
        contents.Children.Add(
            new Label
            {
                Text = string.Format(
                    "References alive at time of creation: {0}",
                    pageRefs.Count(p => p.IsAlive)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            });

        page.Content = contents;
        return page;
    }
}

当您单击“下一页”按钮时,将创建一个带有固定值标签的新页面,该标签显示在创建此页面时有效的页面引用数。每次单击按钮时,您显然会看到这个数字增加 1。我的理解是,当您在导航页面上单击“返回”时,视图应该从堆栈中弹出并丢弃(允许它被 GC) .但是,当我运行这个测试代码时,它表明我们返回后,这个视图被保留在内存中。这可以通过单击 Next Page 几次直到引用计数为 3 来演示。如果您然后单击 Back 和 Next Page,我相信引用计数应该仍然是 3(表明旧页面在新页面之前被 GC一个已创建)但是新的引用计数现在是 4。

这似乎是 iOS 的 X-Forms 导航实现中的一个非常严重的错误(我还没有在其他平台上测试过),我猜它与这里描述的强引用循环问题有某种关系:http://developer.xamarin.com/guides/cross-platform/application_fundamentals/memory_perf_best_practices/

有没有其他人遇到过这种情况和/或提出解决方案/解决方法?其他人会同意这是一个错误吗?

作为补充,我做了第二个不涉及 NavigationPage 的示例(因此必须改用 PushModalAsync),发现我遇到了同样的问题,所以这个问题看起来并不是 NavigationPage 导航所独有的。作为参考,该(非常相似)测试的代码在这里:

public class AppModal
{
    private static List<WeakReference> pageRefs = new List<WeakReference>();

    public static Page GetMainPage()
    {
        return CreateWeakReferencedPage();
    }

    private static Page CreateWeakReferencedPage()
    {
        GC.Collect();
        var result = CreatePage();
        pageRefs.Add(new WeakReference(result));

        // Add a second unreferenced page to prove that the problem only exists
        // when pages are actually navigated to/from
        pageRefs.Add(new WeakReference(CreatePage()));
        GC.Collect();
        return result;
    }

    private static Page CreatePage()
    {
        var page = new ContentPage();
        var contents = new StackLayout();

        contents.Children.Add(
            new Button
            {
                Text = "Next Page",
                Command = new Command(() => page.Navigation.PushModalAsync(CreateWeakReferencedPage()))
            });
        contents.Children.Add(
            new Button
            {
                Text = "Close",
                Command = new Command(() => page.Navigation.PopModalAsync())
            });
        contents.Children.Add(
            new Label
            {
                Text = string.Format(
                    "References alive at time of creation: {0}",
                    pageRefs.Count(p => p.IsAlive)),
                HorizontalOptions = LayoutOptions.CenterAndExpand
            });

        page.Content = contents;
        return page;
    }
}

【问题讨论】:

  • 已经为此提出了一个错误,可以找到here
  • 根据我的经验,Xamarin 的垃圾收集器坏了。他们会说它不是,释放和打破周期取决于你,但这很荒谬。它基本上承认 Xamarin 只会在开发人员明确处理它们之后收集对象的僵尸外壳。你必须明确地处理你所有的控制器、子控制器、视图和子视图。随处处置一切。垃圾收集是 Xamarin 中开发人员的责任。

标签: xamarin.ios xamarin xamarin.forms


【解决方案1】:

我认为您看到的是异步导航的副作用,而不是内存泄漏。您可以选择终结器而不是 WeakReferences,并创建 MyPage 的实例(而不是 ContentPage)。

    public class MyPage: ContentPage
    {
        private static int count;

        public MyPage()
        {
            count++;
            Debug.WriteLine("Created total " + count);
        }
        ~MyPage()
        {
            count--;
            Debug.WriteLine("Finalizer, remaining " + count);
        }
    }

下一个技巧是添加延迟的 GC.Collect() 调用,例如:

    private static Page CreateWeakReferencedPage()
    {
        GC.Collect();
        var result = CreatePage();
        var ignore = DelayedGCAsync();
        return result;
    }

    private static async Task DelayedGCAsync()
    {
        await Task.Delay(2000);
        GC.Collect();
    }

您会注意到实例在此延迟收集(输出窗口)中被垃圾收集。 根据 Xamarin GarbageCollector:我怀疑它是否存在严重缺陷。这里和那里的一个小错误,但不是那么大。也就是说,在 Android 中处理垃圾回收特别棘手,因为其中有两个——Dalvik 和 Xamarin。但那是另一回事了。

【讨论】:

    猜你喜欢
    • 2011-02-20
    • 2013-08-12
    • 2018-08-24
    • 2013-01-08
    • 2013-11-12
    • 2015-05-04
    • 1970-01-01
    相关资源
    最近更新 更多