2014-09-12 35 views
11

任何曾经尝试过的Xamarin.Forms ListView和包含Image视图的ItemTemplate?现在,当ListView包含约20行或更多行时会发生什么?Android上的Xamarin.Forms ListView OutOfMemoryError异常

至于我,我有一个大约4K大小的.png文件加载到图像视图。在应用程序崩溃之前显示的最大9 - 12行OutOfMemoryError。在Android Manifest中请求一个大堆之后,该应用程序在60-70行之后崩溃。我知道Xamarin正在推广使用BitmapFactory类来缩小位图,但是这对于Xamarin Forms Image View并不适用(开箱即用)。

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

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

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

展望未来...

+0

什么的4K PNG的位图大小? PNG存储在内存中而不压缩。当转换为位图时,可以创建一个超过1GB数据的4K PNG。 另外,是的,你真的需要检查位图是否处置。也许答案是,不,他们不是。 – Frank 2014-09-12 11:35:03

+0

我目前使用的PNG被定义为512 x 512. – Avrohom 2014-09-12 12:02:01

+0

因此,4kB PNG需要512 x 512 x 32位= 1MB的RAM来存储/显示。所以你很可能不会处置它们。 – Frank 2014-09-12 12:04:33

回答

10

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

因此,肯定需要在我们自己的手中使用maters来处理图像及其底层资源(位图或可绘制,但是您想称之为)。基本上,它归结为处理原生的'ImageRenderer'对象。

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

因此,除了为Image类和它的(Android)Renderer子类并从内部销毁这个Renderer本身(作为参数传递'true')之外,我没有别的选择。不要尝试'false “)。在渲染器内部,我挂钩页面消失(如果是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); 
      } 
     } 
    } 

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

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

快乐编码,

Avrohom

+0

感谢您分享您的代码和解释。只有一件事,我可以在代码中使用ImageCell吗?我尝试了一个自定义ViewCell,但无法使其工作。干杯 – user1667474 2014-09-29 13:35:20

+0

从来没有与ImageCell尝试过。什么阻止你使用ViewCell?如果你坚持使用ImageCell,那么我认为改变'GetContainingViewCell'方法中的代码就是一个好主意,只要你有'ViewCell'就可以用'ImageCell'取代它。我看不到它不应该做的一个理由。 – Avrohom 2014-09-29 16:12:41

+0

更重要的是,您可以将'ViewCell'改为'Cell',这样它们就可以同时使用。 – Avrohom 2014-09-29 16:13:43

2

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

似乎有在Android涉及的列表视图中的内存泄漏与定制单元。我做了一些调查,发现如果我没有在我的网页如下:

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

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

设置在清单中的Android选项使用大量堆(机器人:largeHeap =“真”),最后,用新的垃圾收集器,(在environment.txt中,MONO_GC_PARAMS = bridge-implementation = new) 这种组合的东西似乎可以解决崩溃问题。我只能假设这只是因为将事情设置为null,有助于GC处置元素,大堆大小购买GC时间,以及新的GC选项来帮助加速收集本身。我真诚地希望这可以帮助别人......

+0

我认为你仍然有内存泄漏。用largeHeap =“true”解决事情真的是一个坏主意。它将在具有更多内存的设备上工作,但“小”设备不会有额外的内存可用。你只是推迟了崩溃。 – 2016-11-28 07:57:22

+0

你的正确,这就是为什么我建议,只是为了让垃圾收集器通过并释放空间而花时间。我有一个列表视图,每个单元格有多个图像和文本,每次在一个屏幕上显示5到10个单元格(取决于屏幕空间),包含整个列表的数百部分,具有256 MB RAM的设备,处理它没有崩溃。这只是一个创可贴,直到Xamarin人可以修复内存泄漏。 – TChadwick 2016-11-29 15:40:45

-2

在Visual Studio 2015年进入调试选项> .droid属性> Android的选项>高级,并设置Java的最大堆大小达到1G

相关问题