Android ViewHolder模式内存泄漏

时间:2014-01-05 11:42:39

标签: android performance android-listview memory-leaks

我在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);
    }
}

4 个答案:

答案 0 :(得分:1)

我在RecyclerViews中遇到了一个问题,因为当Fragment被破坏时,RecyclerView没有从适配器取消注册,因此泄漏了Fragment的View层次结构。

可能存在内存泄漏,与ViewHolders无关。

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

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

查看this,您可以在那里找到完整的解释。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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

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

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

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

答案 3 :(得分:0)

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