2

I am using the below code for my getView() in BaseAdapter.

When I try to rotate the phone a few times, each time the heap memory is increasing. While I analyzed this in the memory analyser, I found that new TextView's are being created, but the old ones are not being destroyed.

What should I do to fix this?

Full Adapter code:

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);
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Peter
  • 21
  • 1
  • 4
  • Something's holding a reference to them so they are not garbage collected but that's not shown in the code snippet. – laalto Jan 05 '14 at 11:48
  • maybe you are leaking your activity reference, – marcinj Jan 05 '14 at 11:51
  • can you please give the full adapter class code?, I think you might be leaking you activity context. there is no problem in this code, this code is fine – Kapil Vats Jan 05 '14 at 12:20
  • have you declared anything as static in your activity? Do you pass the activity context anywhere else? – Simon Jan 05 '14 at 12:27
  • I have update the full adapter code @KapilVats – Peter Jan 06 '14 at 11:47
  • @Simon I am not passing the activity context, but i m passing the application context to a helper class which provides inflaters – Peter Jan 06 '14 at 11:49
  • The adapter code seems fine. How are you releasing and loading the application's context into your helper class? – Ernir Erlingsson Jan 06 '14 at 20:24
  • why are u not using activity context in the constructor? – Kapil Vats Jan 07 '14 at 07:22
  • @KapilVats i dont want to use the activity context which gets changed at any time, instead i am using the global application context – Peter Jan 07 '14 at 09:07
  • @ErnirErlingsson - I am loading the application context using onCreate of a class that extends android.app.Application class – Peter Jan 07 '14 at 09:10
  • what do mean "gets changed at any time", if you are showing some list, that in in your activity, and if activity is getting killed then you adapter should also get freed, I m not getting why r u using application Context, and because of this only this is not getting garbage collected. – Kapil Vats Jan 07 '14 at 09:11
  • @marcin_j - Is there a way to find where my activity reference is being leaked exactly – Peter Jan 07 '14 at 09:13
  • @Peter - yes, you should dump HPROF file using DDMS, and use memory analyzer to compare memory state between different points in time, it should show you increase in number of references in any leak exists – marcinj Jan 07 '14 at 09:15
  • @KapilVats - Adapter is being used in fragment, and on rotation i am not destroying the fragment and reattaching to the new activity – Peter Jan 07 '14 at 09:17
  • use setRetainInstance(true) in your fragment – Kapil Vats Jan 07 '14 at 09:20
  • @KapilVats already using it – Peter Jan 07 '14 at 09:25
  • @marcin_j - Ya, the count is increasing, but how to find from which part of code it is being leaked :( – Peter Jan 07 '14 at 09:29

4 Answers4

2

I've experienced a problem in RecyclerViews leaking the View hierarchy of the Fragment, due to the RecyclerView not unregistering from the Adapter when the Fragment is destroyed.

Your memory leak may there, and have nothing to do with the ViewHolders.

As a quick solutions, you could unregister the RecyclerView from the Adapter in #onDestroyView():

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

Have a look at this, where you'll find the full explanation.

Community
  • 1
  • 1
GaRRaPeTa
  • 5,459
  • 4
  • 37
  • 61
0

Whilst the TextViews are being retained in memory, that doesn't mean that it is your use of the View holder pattern which is the culprit.

In this case, I believe it is your Filter - you are creating an inner anonymous class which keeps a reference to the Adapter, which keeps a reference to the Inflater, which keeps reference to the Context that created it.

Filters (performFiltering) are run in a background thread, and will be keeping your activity alive longer than it wants to be.

Try moving the implementation of Filter into a separate class, or as a static inner class.

FunkTheMonk
  • 10,908
  • 1
  • 31
  • 37
  • I m using the application context to create inflater, but not the activity context Anyway i will give it a try and update here – Peter Jan 07 '14 at 09:27
-1

you should not use application context in your list adapter, Please use the activity context. if you are using in fragment and dont want to re create it the use Fragment's setRetainInstance(boolean) true, but dont use application context.

Kapil Vats
  • 5,485
  • 1
  • 27
  • 30
  • Explain _why_ you shouldn't use application context in a list adapter? – Colin M. May 05 '14 at 20:32
  • If I am reading the above blog correctly then the opposite seems to be true - it's better to use application context over activity context. A direct quote says "The second solution [to avoid memory leaks] is to use the `Application` context." – Max Worg Feb 07 '15 at 11:53
  • Hey Max, It also says "Do not keep long-lived references to a context-activity", means if the reference has to be short-lived then use activity context. and for more clear explanation `http://stackoverflow.com/questions/7298731/when-to-call-activity-context-or-application-context' – Kapil Vats Feb 07 '15 at 15:31
-1

Have you tried to declare the TextView's in your ViewHolder separately instead of inlining them together?

IgorGanapolsky
  • 26,189
  • 23
  • 116
  • 147