0

Gist: custom adapter gets file resource indirectly via filepath in database. Inefficiency / memory concerns. Your opinion requested.

References to all searches, related links, and things useful re topic are at post bottom.

Code below works, but several factors are of concern. Need some more experienced eyes on this please to suggest improvement or potential errors to avoid. App doesn't need to be a content provider (data sourced local to app only). The ListView in question will be very light weight with only about 5 to max 10 entries. (I left out the database stuff because it works.)

Overview:

  • DataBase contains some text and an Image File path. - OK
  • image files are stored on device (SD card / external storage, where ever). - OK

That the files are not in the database makes this different than a normal SimpleCursorAdapter - have to pull the image file. Added overhead of making it into a thumbnail before populating the listview.

As said, it's light, however, even with only one or two entries, the VM is burping. I suspect it's all the memory joggling related to the Bitmaps:

08-27 19:53:14.273: I/dalvikvm-heap(11900): Grow heap (frag case) to 4.075MB for 1228816-byte allocation
08-27 19:53:14.393: D/dalvikvm(11900): GC_CONCURRENT freed <1K, 5% free 4032K/4244K, paused 13ms+3ms, total 116ms

/* myTextAndImageCursorAdapter.java */

import android.widget.SimpleCursorAdapter;

//import android.support.v4.widget.SimpleCursorAdapter;
import android.content.Context;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import android.database.Cursor;
import java.io.File;

import android.widget.ImageView;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import static android.media.ThumbnailUtils.extractThumbnail;


public class TextAndImageCursorAdapter extends SimpleCursorAdapter { 

    private Context context;
    private int layout;

    public TextAndImageCursorAdapter (Context ctx, int layout, Cursor c, String[] from, int[] to) {
        super(context, layout, c, from, to);
        this.context = ctx;
        this.layout = layout;
    }

    @Override
    public View newView(Context ctx, Cursor cursor, ViewGroup parent) {

        Cursor c = getCursor();

        final LayoutInflater inflater = LayoutInflater.from(ctx);
        View vView = inflater.inflate(layout, parent, false);

        int iCol_Text = c.getColumnIndex(DBCOL_TEXT);
        int iCol_Image = c.getColumnIndex(DBCOL_IMAGE);

        String sText = c.getString(iCol_Text);
        String sFileAndPath_Image = c.getString (iCol_Image);  //// sImage path & file

        TextView tvText = (TextView) v.findViewById(R.id.gui_text);
        if (tvText != null) {
            tvText.setText(sSomeText);
        }

        ImageView ivImage (ImageView) v.findViewById(R.id.gui_image);
        if (ivImage != null) {
            ivImage.setImage (mySetImage (sFileAndPath_Image) );
        }

        return vView;
    }

    @Override
    public void bindView(View v, Context ctx, Cursor c) {
        //// ( like newView(), without an inflater, view, or return ) 
        int iCol_Text = c.getColumnIndex(DBCOL_TEXT);
        int iCol_Image = c.getColumnIndex(DBCOL_IMAGE);

        String sText = c.getString(iCol_Text);
        String sFileAndPath_Image = c.getString (iCol_Image);  //// path & file

        TextView tvText = (TextView) v.findViewById(R.id.gui_text);
        if (tvText != null) {
            tvText.setText(sSomeText);
        }

        ImageView ivImage (ImageView) v.findViewById(R.id.gui_image);
        if (ivImage != null) {
            ivImage.setImageBitmap ( mySetImage ( sFileAndPath_Image ) ) ;
        }
    }
    /////
    /////
    protected Bitmap mySetImage ( String path ) {
        int width = 60; int height = 40 ;

        File imgFile = new File ( path );  //// usually like: \sdcard0\wherever\filename1234.bmp
        Bitmap myBitmap = null;

        if( imgFile.exists() )
        {
                myBitmap = BitmapFactory.decodeFile ( imgFile.getAbsolutePath () );                  
        }
            else                    
                Log.d ("oops", "no image file ... using default.");
                myBitmap = getTheDefaultImage ();  //// not shown - this is arbitrary
        }

        imgFile.close();
        return ( extractThumbnail ( myBitmap, width, height ) ) ;
    }      
}

[EDIT - added links] Search Criteria: " Android custom simplecursoradapter with image from file with path in database "

The nearest hit, but attempts to pull image from res, not from external / sd store (unanswered): SimpleCursorAdapter how to show an image?

Also a near hit / miss - similar algo (unanswered) references 4): Customizing list shown from SimpleCursorAdapter using ViewBinder

Almost, but OP's code doesn't work, (no working answer): Load Image in a custom list by SimpleCursorAdapter

Working for OP, but uses JSON for remote retrieval, not local (maybe this could be tweaked, but it's not clear to me how). How to show images in imageview in simple adapter?

Not quite, but again close: ListView scroll slow while loading image from Internal Storage

Image Loader problems (references 2): Imageloader not loading image on real device

Related links:

Android Custom Cursor Adapter

Android: Issue with newView and bindView in custom SimpleCursorAdapter

Similarly named hits, but unrelated to my specific questions - these usually refer to in-app RESources: show image from database where you saved the path of image

Custom SimpleCursorAdapter error

Custom SimpleCursorAdapter, database query and NullPointerException

nullPointerException with extended SimpleCursorAdapter

Android SimpleCursorAdapter - Adding conditional images

External References:

0) Simple intro tut on custom cursor adapters http://thinkandroid.wordpress.com/2010/01/11/custom-cursoradapters/

1) Romain Guy - basic layout ... 2 txts, 1 image http://www.curious-creature.org/2009/02/22/android-layout-tricks-1/

2) AQuery (Android Query) http://code.google.com/p/android-query/wiki/ImageLoading

3) Android thumbnails http://developer.android.com/reference/android/media/ThumbnailUtils.html

4) Cust. listview with an "on/off" star image: http://enjoyandroid.wordpress.com/2012/03/12/customizing-simple-cursor-adapter/

Community
  • 1
  • 1
Howard Pautz
  • 415
  • 7
  • 21

1 Answers1

5

Two things you can do:

1) Use the ViewHolder pattern, cache the LayoutInfalter and most important: don't bind data twice:

/* ... imports */

import static android.media.ThumbnailUtils.extractThumbnail;

public class TextAndImageCursorAdapter extends SimpleCursorAdapter { 

    private LayoutInflater mLayoutInflater;
    private Context context;
    private int layout;


    private class ViewHolder {
        TextView textView;
        ImageView imageView;

        ViewHolder(View v) {
            textView = (TextView) v.findViewById(R.id.gui_text);
            imageView = (ImageView) v.findViewById(R.id.gui_image);
        }
    }

    public TextAndImageCursorAdapter (Context ctx, int layout, Cursor c, String[] from, int[] to) {
        super(ctx, layout, c, from, to);
        this.context = ctx;
        this.layout = layout;
        mLayoutInflater = LayoutInflater.from(ctx);
    }


    @Override
    public View newView(Context ctx, Cursor cursor, ViewGroup parent) {
        View vView = mLayoutInflater.inflate(layout, parent, false);
        vView.setTag( new ViewHolder(vView) );
        // no need to bind data here. you do in later
        return vView;// **EDITED:**need to return the view
    }

    @Override
    public void bindView(View v, Context ctx, Cursor c) {
        // you might want to cache these too
        int iCol_Text = c.getColumnIndex(DBCOL_TEXT);
        int iCol_Image = c.getColumnIndex(DBCOL_IMAGE);

        String sText = c.getString(iCol_Text);
        String sFileAndPath_Image = c.getString (iCol_Image);  //// path & file

        ViewHolder vh = (ViewHolder) v.getTag();

        vh.textView.setText(sSomeText);
        vh.imageView.setImageBitmap ( mySetImage ( sFileAndPath_Image ) );
    }
}

2) This is really important: don't create a thumbnail on every bind. you need to cache the result:

private void setThumbnail(String path, Bitmap b) {
    // save thumbnail to some kind of cache
    // see comment below
}

private Bitmap getThumbnail(String path) {
    Bitmap thumbnail = null;
    // try to fetch the thumbnail from some kind of cache
    // see comment below
    return thumbnail;
}

protected Bitmap mySetImage ( String path ) {
    int width = 60; int height = 40 ;

    Bitmap thumbnail = getThumbnail(path); // try to fetch thumbnail
    if (thumbnail != null) return thumbnail;

    File imgFile = new File ( path );  //// usually like: /sdcard/wherever/filename1234.bmp
    Bitmap myBitmap = null;

    if( imgFile.exists() ) {
            myBitmap = BitmapFactory.decodeFile ( imgFile.getAbsolutePath () );                  
    } else {
            Log.d ("oops", "no image file ... using default.");
            myBitmap = getTheDefaultImage ();  //// not shown - this is arbitrary
    }

    imgFile.close();
    thumbnail = extractThumbnail ( myBitmap, width, height );
    myBitmap.recycle();
    setThumbnail(path, thumbnail); // save thumbnail for later reuse
    return thumbnail;
}     

Depending on you use case, you want to fill getThumbnail() and setThumbnail() with some kind of LruCache:

EDIT :

  @Override
public View newView(Context ctx, Cursor cursor, ViewGroup parent) {
    View vView = mLayoutInflater.inflate(layout, parent, false);
    vView.setTag( new ViewHolder(vView) );
    // no need to bind data here. you do in later
    return vView;// **EDITED:**need to return the view
}
Mehul Joisar
  • 15,348
  • 6
  • 48
  • 57
flx
  • 14,146
  • 11
  • 55
  • 70
  • thx! - am reviewing ... here's a related link about the ViewHolder pattern: http://stackoverflow.com/questions/12223293/cursoradapter-bindview-optimization (viele Grusse an Hamburg from Florida :)) – Howard Pautz Aug 30 '13 at 15:47
  • clarification of differences between newView() and bindView(): http://stackoverflow.com/questions/12672749/what-bindview-and-newview-do-in-cursoradapter (The answer has a great explanation.) – Howard Pautz Aug 30 '13 at 16:19
  • 1
    Actually I'm living in Sri Lanka now ;) – flx Aug 31 '13 at 02:39
  • 1
    dont forget this: http://developer.android.com/reference/android/graphics/Bitmap.html#recycle%28%29 – Diogo Bento Sep 03 '13 at 18:05
  • @DiogoBento - yes, thx ! It's not clear to me when using a ListView (or a view generally) where and when a call to Bitmap.recycle () should happen. Is the view just pointing to the bitmap, or is the bitmap actually copied into the view ? (latter would seem wasteful.) @ flx - Sri Lanka ?! Sehr Schoen ! – Howard Pautz Sep 03 '13 at 19:14
  • I just added the `recycle()` call to the answer. Is everything showing fluently now? – flx Sep 04 '13 at 04:16
  • Trying it now ... did some minor syntax / typo fixes to your answer. – Howard Pautz Sep 04 '13 at 19:36
  • btw: why did you awarded the bounty, but did not accept? is there still anything wrong? – flx Sep 05 '13 at 13:00
  • @flx - still working on it, but it looks like this is good - thx ! – Howard Pautz Sep 06 '13 at 15:56
  • @flx another question, if you don't mind. In your answer, first code section, bindView(), you say: // you might want to cache these too int iCol_Text = c.getColumnIndex(DBCOL_TEXT); ... but I'm unclear what really needs to be cached there. The int's, the cursor itself, or ? And would that only be necessary if the data set was gigantic ? thanks again for your assistance - much appreciated ! – Howard Pautz Sep 09 '13 at 17:12
  • I mean the ints. Not sure, how cost intensive these getColumnIndex() calls are, but if you don't do too strange things with your queries, they should return the same vales every time. Any you are asking a lot for the same values. – flx Sep 10 '13 at 01:38