Memory Leaks and GridView

239 views Asked by At

I am using a GridView and universalimageloader (1.8.6) and seem to be encountering a memory leak - though maybe I am misinterpreting DDMS and MAT results? This is code I did not write, but it is pretty basic - we are showing a number of photos and allowing the user to select as many as they want and then storing those for future reference. The code seems to work fine, but in MAT "Leak Suspect" the GridView from below keeps on showing up, chewing upwards of 5 mb each time, even when I have called finish() on the Activity. From what I have read Android can keep the Activity in memory until it wants to release it (and have seen this with other Activities) but it never seems to want to release this one - even when I force GC. The "new thread" allocation looks a bit suspicious, but wouldn't that get dellocated with the calling Activity?

Probably just missing something obvious, but here is the code:

public class PhotoGalleryPickerActivity extends MyActivity {

private Boolean mMultiple = false;

GridView gridGallery;
Handler handler;
GalleryAdapter adapter;

ImageView imgNoMedia;
Button btnGalleryOk;

String action;
private ImageLoader imageLoader;


@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {

}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_photo_gallery_picker);

    ActionBar actionBar = getActionBar();
    actionBar.setDisplayHomeAsUpEnabled(true);
    actionBar.setTitle("Photo Gallery Capture");

    Bundle extras = getIntent().getExtras();
    mMultiple = extras.getBoolean("multiple");

    initImageLoader();
    init();
}


private void initImageLoader() {
    try {
        String CACHE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/.temp_tmp";
        new File(CACHE_DIR).mkdirs();

        File cacheDir = StorageUtils.getOwnCacheDirectory(getBaseContext(), CACHE_DIR);

        DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
                .cacheOnDisc(true).imageScaleType(ImageScaleType.EXACTLY)
                .bitmapConfig(Bitmap.Config.RGB_565).build();
        ImageLoaderConfiguration.Builder builder = new ImageLoaderConfiguration.Builder(
                getBaseContext())
                .defaultDisplayImageOptions(defaultOptions)
                .discCache(new UnlimitedDiscCache(cacheDir))
                .memoryCache(new WeakMemoryCache());

        ImageLoaderConfiguration config = builder.build();
        imageLoader = ImageLoader.getInstance();
        imageLoader.init(config);

    } catch (Exception e) {
        Utilities.logException(e);
    }
}

private void init() {

    handler = new Handler();
    gridGallery = (GridView) findViewById(R.id.gridGallery);
    gridGallery.setFastScrollEnabled(true);
    adapter = new GalleryAdapter(getApplicationContext(), imageLoader);
    PauseOnScrollListener listener = new PauseOnScrollListener(imageLoader, true, true);
    gridGallery.setOnScrollListener(listener);

    if (mMultiple == true){
        findViewById(R.id.llBottomContainer).setVisibility(View.VISIBLE);
        gridGallery.setOnItemClickListener(mItemMulClickListener);
        adapter.setMultiplePick(true);
    } 
    else {
        findViewById(R.id.llBottomContainer).setVisibility(View.GONE);
        gridGallery.setOnItemClickListener(mItemSingleClickListener);
        adapter.setMultiplePick(false);
    }

    gridGallery.setAdapter(adapter);
    imgNoMedia = (ImageView) findViewById(R.id.imgNoMedia);

    btnGalleryOk = (Button) findViewById(R.id.btnGalleryOk);
    btnGalleryOk.setOnClickListener(mOkClickListener);

    new Thread() {

        @Override
        public void run() {
            Looper.prepare();
            handler.post(new Runnable() {

                @Override
                public void run() {
                    adapter.addAll(getGalleryPhotos());
                    checkImageStatus();
                }
            });
            Looper.loop();
        };

    }.start();

}

private void checkImageStatus() {
    if (adapter.isEmpty()) {
        imgNoMedia.setVisibility(View.VISIBLE);
    } else {
        imgNoMedia.setVisibility(View.GONE);
    }
}

View.OnClickListener mOkClickListener = new View.OnClickListener() {

    @Override
    public void onClick(View v) {
        ArrayList<CustomGallery> selected = adapter.getSelected();

        String[] photos = new String[selected.size()];
        for (int i = 0; i < photos.length; i++) {
            photos[i] = selected.get(i).sdcardPath;
        }

        Intent data = new Intent().putExtra("photos", photos);
        if(photos.length == 0) {
            data = null;
        }
        setResult(RESULT_OK, data);
        finish();

    }
};
AdapterView.OnItemClickListener mItemMulClickListener = new AdapterView.OnItemClickListener() {

    @Override
    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
        adapter.changeSelection(v, position);

    }
};

AdapterView.OnItemClickListener mItemSingleClickListener = new AdapterView.OnItemClickListener() {

    @Override
    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
        CustomGallery item = adapter.getItem(position);
        String[] photos = new String[1];
        photos[0] = item.sdcardPath;
        Intent data = new Intent().putExtra("photos", photos);
        setResult(RESULT_OK, data);
        finish();
    }
};

private ArrayList<CustomGallery> getGalleryPhotos() {
    ArrayList<CustomGallery> galleryList = new ArrayList<CustomGallery>();

    try {
        String[] dirs = new String[1]; 
        final String where =  MediaStore.Images.Media.DATA + " not like ? ";
        String mediaDir = GlobalState.getInstance().currentForm.mediaDirectory();
        if (mediaDir != null) {
            int slash = mediaDir.lastIndexOf("/");
            dirs[0] = mediaDir.substring(0, slash) + "%";
        }
        final String[] columns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID };
        final String orderBy = MediaStore.Images.Media._ID;

        Cursor imagecursor = null;
        try {
            if (mediaDir != null && mediaDir.trim().length() > 0) {
                imagecursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, where, dirs, orderBy);
            }
            else {
                imagecursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, orderBy);
            }

            if (imagecursor != null && imagecursor.getCount() > 0) {

                while (imagecursor.moveToNext()) {
                    CustomGallery item = new CustomGallery();

                    int dataColumnIndex = imagecursor
                            .getColumnIndex(MediaStore.Images.Media.DATA);

                    item.sdcardPath = imagecursor.getString(dataColumnIndex);

                    galleryList.add(item);
                }
            }
        }
        catch (Exception ex) {
            Utilities.logException(ex);
            Utilities.logError("PhotoGalleryPickerActivity", "getGalleryPhotos : " + ex.getMessage());
        } 
        finally {
            if (imagecursor != null) {
                imagecursor.close();
            }
        }

    } catch (Exception e) {
        Utilities.logException(e);
        e.printStackTrace();
    }

    // show newest photo at beginning of the list
    Collections.reverse(galleryList);
    return galleryList;
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            PhotoGalleryPickerActivity.this.finish();
            return true;
    }
    return super.onOptionsItemSelected(item);
}

}

The MAT results, run from Eclipse, look like this. It shows all the various calls I have made to this Activity, as though none of them have been actually released:

Leak Suspect Detail

Leak Suspect Overview

The normal memory for the application hangs around 11-15mb, but as you can see it is now at ~50mb, and grows each time I call the activity. If all the memory for the suspects was reclaimed I think I would be right where I should be:

Heap Allocation

Finally, could this be a result of running from Eclipse remotely to the device? I saw something similar with another control and was not able to replicate. Whereas I am definitely able to replicate this.

Thanks!

1

There are 1 answers

0
Stephen McCormick On

Just to wrap this one up, the issue was the new Thread(), which was holding onto the resources and specifically the GridView (at +4mb per hit). My final solution was to just get rid of the thread and looper, and just call the two methods directly in the init() of the Activity. I have no idea why it was coded into a looping thread to begin with, though I suspect it may have been to update the list of images on the fly (or cut and pasted code that was not really understood). Anyway seems to be working and the memory is being successfully garage collected. Thanks to @dharms you the help!