问题
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/
回答1:
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:
- There is a in memory LruCache available in the android API and in support lib: https://developer.android.com/reference/android/util/LruCache.html
- Jake made an persistent DiskLruCache: https://github.com/JakeWharton/DiskLruCache
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
}
来源:https://stackoverflow.com/questions/18474634/android-custom-simplecursoradapter-with-image-from-file-with-path-in-database