我对Xamarin.Android相当陌生,我一直在阅读不同的站点和解决方案,但是其中大多数都是Java的,或者他们谈论的是一些没有好的文档的库。我的主要问题是,在我滚动应用程序时,它有时会突然崩溃并重新启动,并发出“内存不足”的消息。我当前的适配器具有以下代码:

public override View GetView(int position, View convertView, ViewGroup parent)
{
    var inflater = LayoutInflater.From(parent.Context);
    var view = inflater.Inflate(Resource.Layout.DishesListItem, parent, false);

    var lblTitle = view.FindViewById<TextView>(Resource.Id.Title);
    var imgThumbnail = view.FindViewById<ImageView>(Resource.Id.Thumbnail);

    lblTitle.Text = data[position].name;

    try
    {
        using (StreamReader sr = new StreamReader(Application.Context.Assets.Open(data[position].image)))
        {
            Drawable d = Drawable.CreateFromStream(sr.BaseStream, null);
            imgThumbnail.SetImageDrawable(d);
        }
    }
    catch
    {
        imgThumbnail = null;
    }

    return view;
}


我想澄清一下,我的所有图像都存储在本地,我没有从互联网上下载任何内容,也没有访问SD卡,图片文件夹等。我的图像位于Assets文件夹中,因为它们已链接到SQLite数据库和一些ID。

当前,我仅在OnCreate方法上加载一次适配器。

我找到了此解决方案,但它并不总是有效:
Tips & Tricks for Highly Performing Android ListViews

我注意到,如果您从一个Activity移到另一个Activity,然后又回到ListView所在的Activity,并且开始上下滚动,它只会再次崩溃。

有人曾经经历过吗?如果有的话,您有建议我可以更改什么?

最佳答案

在研究取得成功的结果之后,我想分享我的解决方案,并希望在不久的将来能对许多开发人员有所帮助。我认为这些注释在C#和Java中可能很有用。

1)设计一个非常简单的持有人类别很重要,但这还不够。

    private class MyViewHolder : Java.Lang.Object
    {
        public TextView lblTitle { get; set; }
        public ImageView imgThumbnail { get; set; }
    }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        var inflater = LayoutInflater.From(parent.Context);
        MyViewHolder holder = null;
        var view = convertView;

        if (view != null)
            holder = view.Tag as MyViewHolder;

        if (holder == null)
        {
            holder = new MyViewHolder();
            view = inflater.Inflate(Resource.Layout.DishesListItem, parent, false);
            holder.lblTitle = view.FindViewById<TextView>(Resource.Id.Title);
            holder.imgThumbnail = view.FindViewById<ImageView>(Resource.Id.Thumbnail);
            view.Tag = holder;
        }

        holder.lblTitle.Text = data[position].name;

        try
        {
            holder.imgThumbnail.SetImageDrawable(data[position].image);
        }
        catch
        {
            holder.imgThumbnail = null;
        }

        return view;
    }


2)您需要将非常简单的类连同要加载的信息一起发送给适配器。如果您这样做,则不应发送更多或更少的信息,这将导致新的内存不足。

public class CompactedData
{
    public int id { get; set; }
    public string name { get; set; }
    public Drawable image { get; set; }
}


3)我的直觉告诉我,我应该进一步优化类,并且我需要停止在Adapter中加载任何图像,如果这样做,那么Holder将会变得效率低下,这是事实。适配器不够好,如果要获得最大效率,则需要预加载要添加到holder类的所有图像。

这部分代码是我经验中效率最高的部分,迟早会造成新的内存不足。

try
{
    using (StreamReader sr = new StreamReader(Application.Context.Assets.Open(data[position].image)))
    {
        using (var d = Drawable.CreateFromStream(sr.BaseStream, null))
        {
            imgThumbnail.SetImageDrawable(d);
        }
    }
}
catch
{
    imgThumbnail = null;
}


无论您的映像是否是本地映像,这一点都不会有所不同,您必须事先将其预先加载到共享给Adapter的类中(在我的情况下称为数据)。

编辑:
您也可以按照Skall的建议添加代码来处理变量。

    List<CompactedData> data;

    public PreviewDishesAdapter(List<CompactedData> data)
    {
        this.data = data;
    }


4)最后,如果您的App要多次使用相同的适配器或类似的适配器以及图像,则此代码在每次活动中都是至关重要的,以避免任何可能的崩溃:

    public override void OnBackPressed()
    {
        base.OnBackPressed();
        GC.Collect();
        Finish();
    }


您必须执行垃圾收集器并完成活动,如果您将其保持打开状态,则随时都有新的内存不足。

如果我们大多数人都考虑了所有这些要点,我们可以避免使用“大堆”或修改Google不推荐的其他要点,这是类似主题中非常常见的建议。

最后要考虑的是,如果您要拥有大量图像,则最好创建一些缩略图以减少ListView的加载,这将使您加载更多数据而没有很多问题,因为即使是RecyclerView可能会导致记忆不足;尤其是在使用选项卡或不同的片段时,因为您无法有效预测何时或如何执行垃圾收集器。

您可以使用ImageResizer之类的应用轻松创建它们,只需单击几下,即可避免不必要的麻烦。

10-04 18:09