Till - gotohuman.com
Till - gotohuman.com

Reputation: 6095

Listview in Fragment is causing Memory Leak

I have a FragmentActivity with a FragmentMediaOverview containing a list of MediaItemViews (each with a imageview and some text) and a click on one of the items opening a detail-Fragment. Now when I go back (via back button) and forth (click on listitem) several times from list to detail fragment I eventually run into OOM-Errors. I use SoftReferences for the bitmaps in the listitems as well as in the detail fragment. According to MAT there is an incresing number of MediaItemViews as well as FragmentMediaOverview instances, but I just cannot figure out why.

I read this Android: AlertDialog causes a memory leak , but couldn't solve it nulling out listeners.

Here is my code:

FragmentMediaOverview.java

(This is not a ListFragment because for a tablet-layout the MediaAdapter needs to connect to a gridview)

public class FragmentMediaOverview extends Fragment {
    private static String TAG = FragmentMediaOverview.class.getSimpleName();

    private MediaAdapter adapter;
    private OnMediaSelectedListener selListener;
    private ArrayList<BOObject> mediaItems;

    private ViewGroup layoutContainer;    
    private AdapterView itemContainer; // list or gridview

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView");
        layoutContainer = (ViewGroup) inflater.inflate(R.layout.fragment_media_overview, null);

        return layoutContainer;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        selListener = (OnMediaSelectedListener) activity;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        itemContainer.setOnItemClickListener(null);
        selListener = null;
        adapter = null;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initUi(layoutContainer);
        displayMedia();
    }

    private void initUi(ViewGroup layoutContainer) {
        itemContainer = (AdapterView) layoutContainer.findViewById(android.R.id.list);
        itemContainer.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                BOMedia mediaItem = ((BOMedia) mediaItems.get(position));
//the FragmentActivity is coordinating the FragmentTransactions
                selListener.onMediaSelected(mediaItem);
            }
        });
    }

    private void displayMedia() {
        Log.d(TAG, "Displaying List");
        if (mediaItems == null) {
            loadMedia();
            return;
        }

        Log.d(TAG, "List: " + mediaItems.size() + ", adapter: " + itemContainer.getAdapter());

        if (adapter == null) {
            Log.d(TAG, "Create Adapter with " + mediaItems.size());

            adapter = new MediaAdapter(getActivity(), mediaItems);
        }

        if (itemContainer.getAdapter() == null) {
            itemContainer.setAdapter(adapter);
        } else {
            adapter.setItems(mediaItems);
            adapter.notifyDataSetChanged();
        }

    }

    private void loadMedia() {
        FragmentHelper.showProgressSpinner(layoutContainer, android.R.id.list);
        DbHelper.getInstance().getMedia(mediaType, new DbQueryFinishListener() {

            @Override
            public void onDbCallFinish(ArrayList<BOObject> objects) {
                if (!getActivity().isFinishing()) {
                    mediaItems = objects;
                    Collections.sort(mediaItems, new Comparator<BOObject>() {
                        final Collator c = Collator.getInstance(Locale.GERMAN);
                        @Override
                        public int compare(BOObject s1, BOObject s2) {
                            if (s2 != null && ((BOMedia) s2).getTitle() != null && s1 != null
                                    && ((BOMedia) s1).getTitle() != null) {
                                return c.compare(((BOMedia) s1).getTitle(),((BOMedia) s2).getTitle());
                            } else {
                                return 0;
                            }
                        }
                    });
                    displayMedia();
                    FragmentHelper.hideProgressSpinner(layoutContainer, android.R.id.list);
                }
            }

            @Override
            public void onDbCallException(Exception exception) {
                if (!getActivity().isFinishing()) {
                    FragmentHelper.hideProgressSpinner(layoutContainer, android.R.id.list);
                }
            }
        });

    }
}

MediaAdapter.java

public class MediaAdapter extends BaseAdapter {
    private static final String TAG = MediaAdapter.class.getSimpleName();
    private Context context;
    private ArrayList<BOObject> mediaItems;

    public MediaAdapter(Context c, ArrayList<BOObject> mediaItems) {
        super();
        context = c;
        this.mediaItems = mediaItems;
    }

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

    @Override
    public Object getItem(int position) {
        return null;
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = new MediaItemView(context);
        }
        ((MediaItemView)convertView).initialize((BOMedia) mediaItems.get(position));        
        return convertView;
    }

    public void setItems(ArrayList<BOObject> mediaItems) {
        this.mediaItems = mediaItems;
    }
}

MediaItemView.java

public class MediaItemView extends LinearLayout {
    private static final String TAG = MediaItemView.class.getSimpleName();
    private BOMedia item;
    private SoftReference<Bitmap> bm;
    private ImageView iv;
    private Context ctx;

    public MediaItemView(Context context) {
        super(context);
        LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        layoutInflater.inflate(R.layout.view_media_item, this);
        this.ctx = context;
    }

    /** Init the view with a new BOMedia object
     * @param mediaItem
     */
    public void initialize(BOMedia mediaItem) {
        this.item = mediaItem;
        initUI();
    }

    private void initUI() {
        TextView title = (TextView) findViewById(R.id.itemText);
        iv = (ImageView) findViewById(R.id.itemImage);

        title.setText(Html.fromHtml(item.getTitle()));
        iv.setImageBitmap(null);
        bm = null;
        System.gc();
        iv.invalidate();
        if (item.getFilepathThumb() != null && !item.getFilepathThumb().equals("")) {

            ExpansionPackManager.getInstance().getBitmapResource(item.getFilepathThumb(), false,
                    new BitmapReadListener() {

                        @Override
                        public void onFileRead(BitmapResponseMessage message) {
                            Log.d(TAG, "Bitmap read: " + message.getFilepath());
                            Bitmap image = message.getBitmap();
                            if (image != null && message.getFilepath().equals(item.getFilepathThumb())) {
                                bm = new SoftReference<Bitmap>(image);
                                iv.setImageBitmap(bm.get());
                                Log.d(TAG, "image set");
                            } else {
                                Log.d(TAG, "image too late: " + image);
                            }
                        }

                        @Override
                        public void onFileException(Throwable exception) {
                            Log.d(TAG, "image exception");
                        }
                    });

        }
    }

}

Upvotes: 5

Views: 5593

Answers (3)

Kowlown
Kowlown

Reputation: 1166

In MediaItemView the size of your bitmap must be too big. If the bitmap is 600x600 and you want to display a image with a size of 50x50 you can use Bitmap.createScaledBitmap. You should also use bitmap cache while loading your bitmap.

Upvotes: 2

Shrikant Ballal
Shrikant Ballal

Reputation: 7087

You can also use:

android:hardwareAccelerated = true

Beginning in Android 3.0 (API level 11), the Android 2D rendering pipeline is designed to better support hardware acceleration. Hardware acceleration carries out all drawing operations that are performed on a View's canvas using the GPU.

For more info http://developer.android.com/guide/topics/graphics/hardware-accel.html

Upvotes: 0

Binoy Babu
Binoy Babu

Reputation: 17139

This is because the View for rach child in the ListView is recreated as you scroll through. This is very heavy on resources. To avoid this use a holder class in adapters getView() to hold and reuse the views. This is called an Efficient Adapter. For example see Efficient List Adapter in API demos. http://developer.android.com/tools/samples/index.html

Upvotes: 1

Related Questions