javaandroidmemory-managementmemory-leaks

Are memory leaks normal for Android apps?


I have a really memory hungry app which is really not what I was going for. On average it sits around 90Mb and goes between that and 120Mb. Which is weird because the other night it was only hogging around 40Mb which is obviously more acceptable. My first problem arrives when I simply run the app. The second problem is when I scroll through my ListView I can see the memory getting filled up large chunks at a time. SOoo now I am off hunting memory leaks. I started at a Heap Dump with the new Android Studio feature/button 'Dump Java Heap'. So on displaying all the content of the Heap Dump I noticed really large numbers every now and then ESPECIALLY when it comes to LinkedHashMaps and HashMaps.

https://i.sstatic.net/HmaFA.png

For the amount of work the app does before getting to the ListView it should really not have that amount of memory already used(I Believe) unless it comes standard with every app which I doubt.

Looking at this picture, how do I know which is real memory leaks and how do I find them from here on?

Ok soo I would insert the first 2 functions called right before my ListView will display.

Keep in mind that all my images are loaded with picasso from the internal storage and because it's pretty fast I switched of memory caching also.

This function prepares the adapter and also sends the objectID so that the new intent can fetch the data from the SQLite DB by itself.

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;
    }

}

CODE EDIT 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;
    }

}

Solution

  • I generally consider leaks to be memory that should be freed but isn't, and it's hard for outsiders to know what falls into that category for your app. For your case, if you look near the top of your list, you'll see Picasso's LruCache. Picasso's cache contains a LinkedHashMap, which contains a bunch of Entries (the things immediately above the LruCache), which in this case contains a bunch of bitmaps.

    By default, Picasso will use up to 1/8th of your max memory for a cache, so you don't keep hitting the disk. On a large device, your app may have a couple hundred MB, so this can be fairly large. This is generally desirable, because it keeps your app feeling fast. You might consider it a leak, because it's "wasting" memory and it won't go away, but it's intended.


    So! On to the tool.

    If you click on one of the entries in the class list on the left, and look at the pane to the right, you'll see a list of instances of that type. Click on one. On the bottom pane, you'll see why that object is in memory. If you keep expanding things, you'll generally find code you recognize (e.g. you'll find the View that is using the bitmap, or an LruCache entry for Picasso). Hopefully you don't see an Activity that isn't on screen, because that would generally imply a bad leak.

    The chain of references at the bottom shows you the shortest path to the "GC root" (the ultimate reason it's in memory, e.g. a Thread or static variable). Usually this is the most relevant, but unfortunately not always. There may be other reasons something is kept in memory, e.g. it might be in a cache and currently in use by a view. So it's useful, but not as useful as what you can get with MAT, which shows you all paths, and has much more powerful query tools and whatnot. Hopefully the tools team will add more capabilities eventually, this feature kinda feels like a minimum viable release.

    As an example, here's an app I work on, where I hold onto a cache in my Application which Picasso is using. If you do something similar, and you really want to free memory when the user leaves your app, you can clear the cache when you get e.g. TRIM_MEMORY_UI_HIDDEN: Example heap dump