【问题标题】:Android ViewHolder pattern memory leakAndroid ViewHolder 模式内存泄漏
【发布时间】:2014-01-22 20:43:21
【问题描述】:

我在 BaseAdapter 中为我的 getView() 使用以下代码。

当我尝试旋转手机几次时,每次堆内存都在增加。当我在内存分析器中分析这个时,我发现新的 TextView 正在创建,但旧的并没有被销毁。

我应该怎么做才能解决这个问题?

完整的适配器代码:

package in.mypack.ui;

import static in.mypack.Util.getHelper;
import in.mypack.data.MyClass;
import in.mypack.MyMap;

import java.util.ArrayList;
import java.util.Locale;
import java.util.Map.Entry;

import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.TextView;

public class MyAdapter extends BaseAdapter {

    private Filter filter;
    private MyMap<String, MyClass> items;
    private MyMap<String, MyClass> totalItems;
    private Locale locale;
    private LayoutInflater inflater;
    @SuppressWarnings("unused")
    private final String TAG = "MyAdapter";

    public MyAdapter(MyMap<String, MyClass> objects) {
        items = objects;
        inflater = getHelper().getLayoutInflater();
    }

    private static class ViewHolder {
        TextView one, two, three;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.list_item, null);
            holder = new ViewHolder();
            holder.one = (TextView) convertView.findViewById(R.id.one);
            holder.two = (TextView) convertView.findViewById(R.id.two);
            holder.three = (TextView) convertView.findViewById(R.id.three);
            Typeface font = Typeface.createFromAsset(getHelper().getAssets(), getHelper().getString(R.string.font_custom));
            holder.one.setTypeface(font);
            holder.two.setTypeface(font);
            holder.three.setTypeface(font);
            convertView.setTag(holder);
        }
        else {
            holder = (ViewHolder) convertView.getTag();
        }

        MyClass myObject = getItem(position);
        holder.one.setText(myObject.getName());
        holder.two.setText(myObject.getInfo());
        holder.three.setText(myObject.getSize());
        addColors(convertView, holder, myObject);
        return convertView;
    }

    private void addColors(View convertView, ViewHolder holder, MyClass myObject) {
        if (myObject.isValid()) {
            convertView.setBackgroundColor(Color.argb(255,225,225,225));
            holder.one.setPaintFlags(holder.one.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            holder.two.setPaintFlags(holder.one.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
        }
        else {
            convertView.setBackgroundColor(Color.argb(255,185,185,185));
            holder.one.setPaintFlags(holder.one.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            holder.two.setPaintFlags(holder.one.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
        }
    }

    public Filter getFilter() {
        if (filter == null) {
            locale = Locale.getDefault();
            filter = new Filter() {
                @Override
                protected FilterResults performFiltering(CharSequence query) {
                    FilterResults results = new FilterResults();
                    if (totalItems == null) {
                        totalItems = new MyMap<String, MyClass>();
                        totalItems.putAll(items);
                    }

                    if (query == null || 0 == query.length()) {
                        results.count = totalItems.size();
                        results.values = totalItems;
                    }
                    else {
                        MyMap<String, MyClass> filteredList = new MyMap<String, MyClass>();
                        MyMap<String, MyClass> containsList = new MyMap<String, MyClass>();
                        int size = totalItems.size();
                        for (int i = 0; i < size; i++) {
                            Entry<String, MyClass> entry = totalItems.getEntry(i);
                            if (entry.getValue().getTitle().toLowerCase(locale).startsWith(query.toString().toLowerCase(locale))) {
                                filteredList.putEntry(entry);
                            } else if (entry.getValue().getTitle().toLowerCase(locale).contains(query.toString().toLowerCase(locale))) {
                                containsList.putEntry(entry);
                            }
                        }
                        filteredList.putAll(containsList);
                        results.count = filteredList.size();
                        results.values = filteredList;
                    }
                    return results;
                }

                @SuppressWarnings("unchecked")
                @Override
                protected void publishResults(CharSequence query, FilterResults results) {
                    items.clear();
                    items.putAll((MyMap<String, MyClass>) results.values);
                    notifyDataSetChanged();
                }

            };
        }
        return filter;
    }

    public void filter(String query) {
        getFilter().filter(query);
    }

    @Override
    public int getCount() {
        return items.size();
    }

    @Override
    public MyClass getItem(int index) {
        return items.get(index);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    public void add(MyClass myObject) {
        items.sortOnPut(myObject.getName(), myObject, MyMap.Sorting.Value);
    }
}

【问题讨论】:

  • 某些东西持有对它们的引用,因此它们不会被垃圾收集,但代码 sn-p 中没有显示。
  • 也许你泄露了你的活动参考,
  • 您能否提供完整的适配器类代码?我认为您可能会泄露您的活动上下文。这段代码没有问题,这段代码没问题
  • 您是否在活动中将任何内容声明为静态?您是否将活动上下文传递到其他任何地方?
  • 我已经更新了完整的适配器代码@KapilVats

标签: android performance android-listview memory-leaks


【解决方案1】:

我在 RecyclerViews 中遇到了一个问题,泄漏了 Fragment 的 View 层次结构,因为 RecyclerView 在 Fragment 被销毁时没有从 Adapter 注销。

您的内存泄漏可能在那里,与 ViewHolders 无关。

作为一种快速的解决方案,您可以在#onDestroyView() 中从适配器中取消注册 RecyclerView:

@Override
public void onDestroyView() {
    super.onDestroyView();
    mRecyclerView.setAdapter(null);
}

查看this,您会在其中找到完整说明。

【讨论】:

    【解决方案2】:

    虽然 TextViews 被保留在内存中,但这并不意味着您使用 View holder 模式是罪魁祸首。

    在这种情况下,我相信它是您的过滤器 - 您正在创建一个内部匿名类,它保留对适配器的引用,它保留对 Inflater 的引用,它保留对创建它的上下文的引用。

    过滤器(performFiltering)在后台线程中运行,并且将使您的活动保持比预期更长的时间。

    尝试将 Filter 的实现移动到一个单独的类中,或者作为一个静态内部类。

    【讨论】:

    • 我正在使用应用程序上下文来创建充气器,但不是活动上下文无论如何我会试一试并在这里更新
    【解决方案3】:

    您不应在列表适配器中使用应用程序上下文,请使用活动上下文。 如果您在片段中使用并且不想重新创建它,请使用片段的setRetainInstance(boolean) true,但不要使用应用程序上下文。

    【讨论】:

    • 解释为什么你不应该在列表适配器中使用应用程序上下文?
    • 如果我正确阅读了上面的博客,那么情况似乎正好相反——最好使用应用程序上下文而不是活动上下文。直接引用说“[避免内存泄漏] 的第二种解决方案是使用 Application 上下文。”
    • 嘿 Max,它还说“不要保留对上下文活动的长期引用”,这意味着如果引用必须是短期的,则使用活动上下文。为了更清楚的解释`stackoverflow.com/questions/7298731/…'
    【解决方案4】:

    您是否尝试过在 ViewHolder 中单独声明 TextView,而不是将它们内联在一起?

    【讨论】:

      猜你喜欢
      • 2016-02-02
      • 1970-01-01
      • 2019-09-16
      • 2011-12-31
      • 1970-01-01
      • 1970-01-01
      • 2014-08-19
      • 2012-11-14
      相关资源
      最近更新 更多