问题 Android上的Xamarin.Forms ListView OutOfMemoryError异常


有没有人尝试使用包含图像视图的ItemTemplate的A Xamarin.Forms Listview?现在,当ListView包含大约20行或更多行时会发生什么?

至于我,我有一个大约4K大小的.png文件加载到图像视图中。在使用OutOfMemoryError崩溃应用程序之前,最多显示9到12行。在Android Manifest中请求一个大堆之后,应用程序在60-70行之后崩溃。

我知道Xamarin正在推广使用BitmapFactory类来缩小位图,但这不适用于Xamarin Forms Image View(开箱即用)。

我试图摆弄ImageRenderer的Sub Class,看看我是否可以添加一个BitmapFactory.Options属性,如果这样可以解决问题。

此外,我可能需要检查Xamarin.Forms是否在ViewCell滚动屏幕后处理(回收)包含的位图。

在开始这个旅程之前,我非常希望得到任何可以使这个更简单或更简单的解决方案的评论,认为这个过程是不必要的。

期待...


7399
2017-09-12 11:25


起源

什么是4K PNG的位图大小? PNG存储在内存中而不会压缩。转换为位图时,可以创建一个超过1GB数据的4K PNG。此外,是的,你真的需要检查位图是否被处置。可能答案是,不,他们不是。 - Frank
我目前使用的PNG定义为512 x 512。 - Avrohom
因此4kB PNG需要512 x 512 x 32位= 1MB的RAM来存储/显示。所以很可能你确实没有处理它们。 - Frank
对不起,这只是一点点。这是24位深度...... - Avrohom
我可以肯定地确认在ViewCell中加载的图像视图“永远不会”处理掉。与放置在表单上的图像视图相反。尝试和测试。不错的工作! Xamarin伙计们! - Avrohom


答案:


是的,我找到了解决方案。代码要遵循。但在此之前,让我解释一下我所做的一切。

所以,绝对需要掌握maters来处理图像及其底层资源(位图或可绘制,但是你想要调用它)。基本上,它归结为处置原生的'ImageRenderer'对象。

现在,无法从任何地方获取对该ImageRenderer的引用,因为为了这样做,需要能够调用Platform.GetRenderer(...)。由于其范围被声明为“内部”,因此无法访问“平台”类。

所以,除了对Image类和它的(Android)渲染器进行子类化并从内部销毁这个渲染器本身(传递'true'作为参数时,我别无选择。不要尝试使用'false')。在渲染器内部,我挂钩到页面消失(如果是TabbedPage)。在大多数情况下,Page Disappear事件不能很好地发挥作用,例如当页面仍在屏幕堆栈中但由于正在绘制另一页面而消失 最佳 它如果丢弃图像,则当页面再次被揭开(显示)时,它将不会显示图像。在这种情况下,我们必须挂钩主导航页面的'Popped'事件。

我试图尽我所能地解释。其余的 - 我希望 - 你将能够从代码中获得:

这是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);
            }
        }
    }

最后,我将名称空间中的应用程序名称更改为PCL项目中的“ApplicationClient”和Droid项目中的“Application.Droid”。您应该将其更改为您的应用名称。

此外,Renderer类末尾的几个递归方法,我知道我可以将它组合成一个Generic方法。问题是,当需要出现时,我一次建立一个。所以,这就是我离开它的方式。

编码愉快,

Avrohom


10
2017-09-28 13:31



感谢您分享您的代码和解释。只有一件事,我能用你的代码使用ImageCell吗?我尝试了一个自定义ViewCell,但无法让它工作。干杯 - user1667474
从未尝试使用ImageCell。什么阻碍了您使用ViewCell?如果您坚持使用ImageCell,那么我想在“GetContainingViewCell”方法中更改代码是个好主意,无论您使用“ViewCell”将其替换为“ImageCell”。我看不出它不该做的原因。 - Avrohom
更重要的是,您可以将“ViewCell”更改为“Cell”,以便它可以同时使用。 - Avrohom
我终于让我的自定义ViewCell工作了,但是我必须做其他错误,因为之前我会从我的列表中调用一个然后内存不足但是上面的代码在它显示列表之前就用完了。我不知道我做错了什么 - user1667474
你能告诉你自定义ViewCell的代码吗? - Avrohom


可能有帮助的另一组步骤如下:

Android上涉及使用自定义单元格的listviews似乎存在内存泄漏。我做了一些调查,发现如果我在我的网页上做了以下内容:

    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选项以帮助加速收集本身。我真诚地希望这可以帮助别人......


2
2018-04-15 14:46



我觉得你还有内存泄漏。使用largeHeap =“true”解决问题确实是一个坏主意。它可以在具有更多内存的设备上运行,但“小”设备将无法提供额外的内存。你只是推迟了崩溃。 - Ton Snoei
这是正确的,这就是为什么我建议只是花时间让垃圾收集器通过并释放空间。我有一个listview,每个单元格有多个图像和文本,一次在屏幕上显示5到10个单元格(取决于屏幕空间),在256 MB RAM的设备上显示整个列表的几百个部分,它处理它而不会崩溃。这只是一个创可贴,直到Xamarin人员可以解决内存泄漏问题。 - TChadwick


在Visual Studio 2015中,转到“调试”选项> .droid“属性”>“Android选项”>“高级”,并将“Java最大堆大小”设置为1G


-3
2017-07-13 11:31