Подтвердить что ты не робот

Это утечка памяти в Xamarin Forms?

У меня возникла проблема, когда он появляется. Объекты страницы не являются мусором, собранным после того, как они были удалены. Я собрал очень простой пример этого, который демонстрирует проблему при использовании 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'd), Однако, когда я запускаю этот тестовый код, он указывает, что после того, как мы вернемся, это представление сохраняется в памяти. Это можно продемонстрировать, нажав "Следующая страница" несколько раз, пока счетчик ссылок не будет равен 3. Если вы затем щелкните "Назад" и затем "Следующая страница" , я считаю, что счетчик ссылок должен быть равен 3 (это означает, что старая страница была GC'd до новой один был создан), однако новый счетчик ссылок теперь равен 4.

Это кажется довольно серьезной ошибкой в ​​реализации X-форме навигации для прошивки (я не проверял это для других платформ), мое предположение того, что какое-то образом связанно с сильной проблемой Reference цикла, описанной здесь: 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;
    }
}
4b9b3361

Ответ 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's. Но это еще одна история.