Xamarin.Forms ListView Исключение OutOfMemoryError на Android

Кто-нибудь когда-либо пробовал A Xamarin.Forms Listview с ItemTemplate, содержащим изображение? Итак, что происходит, когда ListView содержит около 20 или более строк?

Что касается меня, у меня есть файл .png размером около 4K, загруженный в представление "Изображение". Получено максимум 9-12 строк, показанных до того, как приложение разбилось с помощью OutOfMemoryError. После запроса большой кучи в манифесте андроида приложение падает после 60 - 70 строк.

Я знаю, что Xamarin продвигает использование класса BitmapFactory для масштабирования растровых изображений, но это неприменимо (из коробки) для Xamarin Forms Image View.

Я собираюсь попробовать с помощью Sub Class ImageRenderer посмотреть, могу ли я добавить свойство BitmapFactory.Options, и если это решит проблему.

Кроме того, мне может потребоваться проверить, удаляет ли Xamarin.Forms(перерабатывает) содержащееся растровое изображение после прокрутки экрана ViewCell.

Прежде чем отправиться в путешествие, я был бы очень заинтересован в том, чтобы получить какие-либо комментарии, которые могли бы сделать это проще или более простым решением, которое считало бы этот процесс ненужным.

В ожидании...

Ответ 1

Да, я нашел решение. Код для подражания. Но прежде, позвольте мне немного объяснить, что я сделал.

Таким образом, определенно необходимо взять маты в наших руках, чтобы избавиться от изображения и его основных ресурсов (растровое или рисованное, однако вы хотите назвать его). По сути, это сводится к утилизации собственного объекта ImageRenderer.

Теперь нет способа получить ссылку на этот ImageRenderer из любого места, потому что для этого нужно иметь возможность вызвать Platform.GetRenderer(...). Доступ к классу "Платформа" недоступен, поскольку его область объявлена ​​как "внутренняя".

Итак, у меня не осталось другого выбора, кроме подкласса класса Image и его (Android) Renderer и уничтожить самого Renderer изнутри (передавая "true" в качестве аргумента. Не пытайтесь использовать "false",). Внутри Renderer я зацепился за страницу (в случае TabbedPage). В большинстве ситуаций событие "Исчезновение страницы" не будет хорошо служить цели, например, когда страница все еще находится в стеке экрана, но исчезает из-за того, что другая страница рисуется на Верх. Если вы выбрали изображение (ы), чем, когда страница будет открыта (показана) еще раз, она не отобразит изображения. В этом случае мы должны подключиться к главному событию навигации "Выбранная".

Я пытался объяснить все, что мог. Остальное - надеюсь, вы сможете получить код:

Это подкласс класса в проекте PCL.

using System;

using Xamarin.Forms;

namespace ApplicationClient.CustomControls
{
    public class LSImage : Image
    {
    }
}

Следующий код находится в проекте Droid.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using Android.Util;
using Application.Droid.CustomControls;
using ApplicationClient.CustomControls;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

    [assembly: ExportRenderer(typeof(ApplicationClient.CustomControls.LSImage), typeof(LSImageRenderer))]

    namespace Application.Droid.CustomControls
    {
        public class LSImageRenderer : ImageRenderer
        {
            Page page;
            NavigationPage navigPage;

            protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
            {
                base.OnElementChanged(e);
                if (e.OldElement == null)
                {
                    if (GetContainingViewCell(e.NewElement) != null)
                    {
                        page = GetContainingPage(e.NewElement);
                        if (page.Parent is TabbedPage)
                        {
                            page.Disappearing += PageContainedInTabbedPageDisapearing;
                            return;
                        }

                        navigPage = GetContainingNavigationPage(page);
                        if (navigPage != null)
                            navigPage.Popped += OnPagePopped;
                    }
                    else if ((page = GetContainingTabbedPage(e.NewElement)) != null)
                    {
                        page.Disappearing += PageContainedInTabbedPageDisapearing;
                    }
                }
            }

            void PageContainedInTabbedPageDisapearing (object sender, EventArgs e)
            {
                this.Dispose(true);
                page.Disappearing -= PageContainedInTabbedPageDisapearing;
            }

            protected override void Dispose(bool disposing)
            {
                Log.Info("**** LSImageRenderer *****", "Image got disposed");
                base.Dispose(disposing);
            }

            private void OnPagePopped(object s, NavigationEventArgs e)
            {
                if (e.Page == page)
                {
                    this.Dispose(true);
                    navigPage.Popped -= OnPagePopped;
                }
            }

            private Page GetContainingPage(Xamarin.Forms.Element element)
            {
                Element parentElement = element.ParentView;

                if (typeof(Page).IsAssignableFrom(parentElement.GetType()))
                    return (Page)parentElement;
                else
                    return GetContainingPage(parentElement);
            }

            private ViewCell GetContainingViewCell(Xamarin.Forms.Element element)
            {
                Element parentElement = element.Parent;

                if (parentElement == null)
                    return null;

                if (typeof(ViewCell).IsAssignableFrom(parentElement.GetType()))
                    return (ViewCell)parentElement;
                else
                    return GetContainingViewCell(parentElement);
            }

            private TabbedPage GetContainingTabbedPage(Element element)
            {
                Element parentElement = element.Parent;

                if (parentElement == null)
                    return null;

                if (typeof(TabbedPage).IsAssignableFrom(parentElement.GetType()))
                    return (TabbedPage)parentElement;
                else
                    return GetContainingTabbedPage(parentElement);
            }

            private NavigationPage GetContainingNavigationPage(Element element)
            {
                Element parentElement = element.Parent;

                if (parentElement == null)
                    return null;

                if (typeof(NavigationPage).IsAssignableFrom(parentElement.GetType()))
                    return (NavigationPage)parentElement;
                else
                    return GetContainingNavigationPage(parentElement);
            }
        }
    }

Наконец, я изменил имя приложения в пространстве имен на "ApplicationClient" в проекте PCL и на "Application.Droid" в проекте Droid. Вы должны изменить его на свое имя приложения.

Кроме того, несколько рекурсивных методов в конце класса Renderer, я знаю, что я мог бы объединить его в один общий метод. Дело в том, что я построил один за раз, когда возникла необходимость. Так вот, как я его оставил.

Счастливое кодирование,

Авраам

Ответ 2

Еще один способ, который может помочь, заключается в следующем:

Похоже на утечку памяти на андроид, включающую списки с настраиваемыми ячейками. Я провел некоторое расследование и обнаружил, что если бы я сделал следующее на своих страницах:

    protected override void OnAppearing()
    {
        BindingContext = controller.Model;
        base.OnAppearing();
    }

    protected override void OnDisappearing()
    {
        BindingContext = null;
        Content = null;
        base.OnDisappearing();
        GC.Collect();
    }

Задайте опцию android в манифесте для использования большой кучи (android: largeHeap = "true" ) и, наконец, использовали новый сборщик мусора (в файле environment.txt, MONO_GC_PARAMS = bridge-implementation = new) Такое сочетание вещей, похоже, устраняет проблему. Я могу только предположить, что это происходит только потому, что настройка вещей в null, помогает GC распоряжаться элементами, большой размер кучи, чтобы купить время GC для этого, и новый вариант GC, чтобы ускорить сбор. Я искренне надеюсь, что это поможет кому-то еще...

Ответ 3

В Visual Studio 2015 Перейдите в пункт "Отладка" > "Свойства .droid" > "Настройки Android" > "Дополнительно" и установите размер Java Max Heap до 1G