5

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

        }
    }

}
Community
  • 1
  • 1
Till - Appviewer.io
  • 4,529
  • 1
  • 31
  • 35
  • Ideas: `ExpansionPackManager` might be holding references to bitaps or else `BitmapResponseMessage`s if they are maintained in a recycle pool like Android `Message`. For the latter some other code would have to call `message.recycle()`. – Gene Jul 06 '12 at 03:53
  • Have a Look on it.. http://stackoverflow.com/questions/9933783/double-checking-if-fragment-view-holder-pattern-is-implemented-properly And I prefere you to watch the [Google IO presentation](http://www.google.com/events/io/2010/sessions/world-of-listview-android.html) about `ListViews`. It contains memory saving techniques and it helped you to solve and understand the logic of the `ListViews`. And Also see this [PDF File.](http://dl.google.com/googleio/2010/android-world-of-listview-android.pdf) – Mehul Jul 04 '12 at 05:24

3 Answers3

2

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.

Kowlown
  • 920
  • 10
  • 26
1

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

Binoy Babu
  • 16,699
  • 17
  • 91
  • 134
  • Thanks for the hint. This might make it a little more efficient, but wouldn't explain a memory leak would it?! As I am reusing the convertView, there shouldn't be any dead ones floating around... I have the feeling that somehow the list items are being reinstantiating on a resume /back press, but old ones are being kept ...somewhere... – Till - Appviewer.io Jun 28 '12 at 13:06
0

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

Shrikant Ballal
  • 7,067
  • 7
  • 41
  • 61