【问题标题】:Memory leaks or normal for Android apps?Android应用程序的内存泄漏或正常?
【发布时间】:2015-08-18 04:29:15
【问题描述】:

我有一个非常需要内存的应用程序,这真的不是我想要的。平均而言,它位于 90Mb 和 120Mb 之间。这很奇怪,因为前几天晚上它只占用了 40Mb 左右,这显然是可以接受的。 当我简单地运行应用程序时,我的第一个问题就出现了。第二个问题是当我滚动浏览我的 ListView 时,我可以看到内存一次被大块填满。 SOoo 现在我正在寻找内存泄漏。 我从带有新的 Android Studio 功能/按钮“Dump Java Heap”的堆转储开始。 因此,在显示堆转储的所有内容时,我不时注意到非常大的数字,尤其是在涉及 LinkedHashMaps 和 HashMaps 时。

对于应用程序在进入 ListView 之前所做的工作量,它实际上不应该已经使用了那么多内存(我相信),除非它是我怀疑的每个应用程序的标准配置。

看这张图,我怎么知道哪个是真正的内存泄漏,从这里开始如何找到它们?

好的,我将在 ListView 显示之前插入前 2 个调用的函数。

请记住,我所有的图片都是从内部存储加载毕加索的,因为它非常快,我也切换了内存缓存。

此函数准备适配器并发送 objectID,以便新 Intent 可以自行从 SQLite DB 中获取数据。

public void DisplayList(final List<ListData> DL)
{

    ListAdapter listAdapter = new ParseAdapter(this,DL);
    ListView listView = (ListView) findViewById(R.id.Parse_list);
    listView.setAdapter(listAdapter);

    listView.setOnItemClickListener(
            new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id)
                {
                    String food = String.valueOf(parent.getItemAtPosition(position));
                    //setContentView(R.layout.deal_showcase);

                    Intent i = new Intent(getApplicationContext(),Deal_Showcase.class);

                    i.putExtra("objectid",DL.get(position).getObjectID());
                    //Intent intent = new Intent(this,DL.class);
                    startActivity(i);

                }
            }

    );
}

//Adapter class
public class ParseAdapter extends ArrayAdapter<ListData>
{

ParseAdapter(Context context, List<ListData> adapResource) {
    super(context, R.layout.parse_list_layout, adapResource);
}

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
        View customView = layoutInflater.inflate(R.layout.parse_list_layout, parent, false);

    //PLease note that after implementing a ViewHolder is did not really do anything for me
    //Also the ViewHolder implementation is not here at the moment but this is the version of           //the code right before the ViewHolder got implemented.
        String singleItem = getItem(position).getName();
        TextView rText = (TextView) customView.findViewById(R.id.pName);

        TextView yText = (TextView) customView.findViewById(R.id.pDistance);
        ImageView image = (ImageView) customView.findViewById(R.id.imageView);


        rText.setText(getItem(position).getName());
        yText.setText(Integer.toString(getItem(position).getDistance()));
        Picasso.with(this.getContext()).load("file://"+getItem(position).getArt_work_uri())
                .resize(200,200)
                .into(image);

        return customView;
    }

}

代码编辑 1:

public class ParseAdapter extends ArrayAdapter<DealListData>
{
    //Inner static class ViewHolder
    static class ViewHolder
    {
    TextView Distance;
    TextView DealName;
    ImageView image;
}


ParseAdapter(Context context, List<DealListData> adapResource) {
    super(context, R.layout.parse_list_layout, adapResource);
}
    View convertView;

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;



        if (convertView==null)
        {

            LayoutInflater layoutInflater = LayoutInflater.from(getContext());
            convertView = layoutInflater.inflate(R.layout.parse_list_layout, parent, false);
            //ViewHolder holder setup
            holder = new ViewHolder();
            holder.DealName = (TextView) convertView.findViewById(R.id.pName);
            holder.Distance = (TextView) convertView.findViewById(R.id.pDistance);
            holder.image = (ImageView) convertView.findViewById(R.id.imageView);

            //Store the holder in the view
            convertView.setTag(holder);
        }
        else
        {
            //Get the holder if it already exists
            holder = (ViewHolder) convertView.getTag();
        }

customView.findViewById(R.id.imageView);

        holder.DealName.setText(getItem(position).getName());
        holder.Distance.setText(Integer.toString(getItem(position).getDistance()));
        Picasso.with(this.getContext()).load("file://"+getItem(position).getArt_work_uri())
                //.memoryPolicy(MemoryPolicy.NO_CACHE )
                .networkPolicy(NetworkPolicy.NO_CACHE)
                .resize(200,200)
                .into(holder.image);

        return convertView;
    }

}

【问题讨论】:

  • 在 Eclipse 中,您有一个名为“泄漏嫌疑人”的按钮,它可能也在 android studio 中,这说明了什么?
  • 我会尽快调查的!
  • 在下面查看我的答案。我想我找到了你的问题。
  • @JohanShogun 我不确定您是否指的是 Dump heap 按钮下方的“开始分配跟踪”按钮。问题是只有 3 个按钮。另一个是垃圾收集器按钮。
  • vogella.com/tutorials/EclipseMemoryAnalyzer/article.html 如果你在 android studio 中没有类似的东西,你可以在 eclipse 中打开 head dump 文件。

标签: java android memory-management memory-leaks


【解决方案1】:

我通常认为泄漏是应该释放但的内存,外人很难知道您的应用程序属于该类别的内存。对于您的情况,如果您查看列表顶部附近,您会看到 Picasso 的 LruCache。 Picasso 的缓存包含一个 LinkedHashMap,其中包含一堆 Entries(紧接在 LruCache 上方的东西),在本例中它包含一堆位图。

默认情况下,Picasso 最多会将您的 max 内存的 1/8 用于缓存,因此您不会一直访问磁盘。在大型设备上,您的应用程序可能有几百 MB,因此这可能相当大。这通常是可取的,因为它可以让您的应用程序感觉快速。您可能认为这是一个泄漏,因为它“浪费”了内存并且它不会消失,但它是有意的。


所以!使用工具。

如果您单击左侧类列表中的一个条目,然后查看右侧的窗格,您将看到该类型的实例列表。单击一个。在底部窗格中,您会看到为什么该对象在内存中。如果您继续扩展内容,您通常会找到您识别的代码(例如,您会找到使用位图的视图,或 Picasso 的 LruCache 条目)。希望您没有看到不在屏幕上的 Activity,因为这通常意味着严重的泄漏。

底部的引用链向您显示“GC 根”的最短 路径(它在内存中的最终原因,例如线程或静态变量)。通常这是最相关的,但不幸的是并非总是如此。可能有其他原因保留在内存中,例如它可能在缓存中并且当前被视图使用。所以它很有用,但不像MAT 那样有用,它向您显示所有 路径,并且具有更强大的查询工具等等。希望工具团队最终会添加更多功能,这个功能有点像最小的可行版本。

例如,这是我正在开发的一个应用程序,我在我的应用程序中保留了毕加索正在使用的缓存。如果你做了类似的事情,并且你真的想在用户离开你的应用程序时释放内存,你可以在你得到例如时清除缓存。 TRIM_MEMORY_UI_HIDDEN:

【讨论】:

  • 您似乎没有位图问题。我自己为原生图像数据实现了类似的缓存+引用计数,这就是毕加索看起来正在做的事情。你也不能轻易地用你自己的代码替换它,因为它 (a) 它的编码相当多 (b) 它看起来很成熟,可能没有严重的内存泄漏。让我们一步一步来。告诉我,您发现我提议的列表更改有什么不同吗?
  • @Umka:没什么真正重要的。然而,该应用确实感觉更流畅了,而且由于某种原因,垃圾收集器现在似乎做得更好了!
  • @Groxx:感谢您的可靠解释。然而,我注意到,即使在我的 ListView 启动或它的任何资源被加载之前,APP 只是跳转到 80Mg +。
  • @Groxx:我如何让我的应用在退出时清除缓存?
【解决方案2】:

列表在 android 和 ios 中都很棘手。主要原则是仅将当前可见的内容保存在内存中。这是您的清单做错了什么的机会。很高兴在这里显示您的列表代码。

您的 getView() 方法似乎遗漏了重要部分。应该是这样的:

        View customView;

        if (convertView != null) {
            customView = convertView;
        } else {
            LayoutInflater layoutInflater = LayoutInflater.from(getContext());
            customView = layoutInflater.inflate(R.layout.parse_list_layout, parent, false);
        }

在您的代码中,您永远不会重复使用已经存在的列表项。滚动浏览列表时,消耗的内存会增加。

另一个特别是 android 的问题是原生数据的垃圾收集,例如您在应用中使用的图像数据。如果您的列表项带有图像,那么很可能您也遇到过这种情况。

【讨论】:

  • 如果您需要其他任何东西,请告诉我。我见过比这个应用程序更多的应用程序,整个设备只有 512Mb。所以我确定我在这里做错了什么。
  • 您的 getView() 方法看起来很可疑。将修改我的答案。
  • 我已经通过帖子更新了第二个代码空间。检查我的原始代码下方的块以获取新代码。
【解决方案3】:

您的getView() 方法不会回收视图,并且每次都会膨胀一个新项目,大约每个膨胀过程大约需要 1.2KB 回收视图以节省内存。

您可能还想观看此视频以更好地了解 Google I/O:

https://www.youtube.com/watch?v=wDBM6wVEO70

其他可能导致内存泄漏的情况是,当您使用大尺寸图像时会超出内存预算并最终崩溃,请确保优化图像

【讨论】:

  • 抱歉耽搁了这么久。好的,让我直接了解毕加索。如果它下载图片......它是否位于 RAM 中并在 RAM 中调整大小。如果图片很大,但在本地检索到,Re-Sizing 是否会导致它可能位于 RAM 中而不会被删除?如果很快那么那肯定是我的问题。让我困扰的是,当应用程序只是加载基础知识而没有开始加载 ListView 时,它跳得太快了 80Mg+
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多