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 на экране.

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

Жду с нетерпением…

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

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

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

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

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

Это подкласс класса в проекте 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, я знаю, что я мог бы объединить его в один универсальный метод. Дело в том, что я построил один за раз, когда возникла необходимость. Таким образом, я оставил это.

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

Авраам

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

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

  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, чтобы ускорить сборку. Я искренне надеюсь, что это поможет кому-то еще …

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