I've just spent the last couple of hours wrestling with OutOfMemory errors when populating a Gallery widget with photos, and as fun as it's been, I think I've sorted it out and thought I'd share my solution with you.
For starters, the reason we're getting OutOfMemory
errors in the first place is that the heap size on Android
devices is limited to something like 16 MB on a G1 and 24 MB on a
Nexus one.
As soon as you start to work with media files you quickly start to learn that you need to work to manage your memory usage with Android as a result of this limitation.
I would also like to thank direct to device debugging, I couldn't have done this without utilizing it, I would go as far as saying that it is essential in situations like this.
Here is the full code of one of my imageAdapters, I'm using this to provide the images to my Gallery widget like this:
galleryTop.setAdapter(new ImageAdapterTop(this));
The Adapter class :
public class ImageAdapterTop extends BaseAdapter {
int mGalleryItemBackground;
private Context mContext;
File[] allTopSlices = Utils.getAllTopSlices();
String[] fullPathAllTopSlices = new String[allTopSlices.length];
Uri[] uriAllTopSlices = new Uri[fullPathAllTopSlices.length];
public ImageAdapterTop(Context c) {
mContext = c;
TypedArray a = obtainStyledAttributes(R.styleable.HelloGallery);
mGalleryItemBackground = a.getResourceId(
R.styleable.HelloGallery_android_galleryItemBackground, 0);
a.recycle();
}
public int getCount() {
return uriAllTopSlices.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
int screenHeightPx = Utils.GetScreenHeight(getWindowManager().getDefaultDisplay());
for(int i = 0; i < allTopSlices.length; i++){
fullPathAllTopSlices[i] = allTopSlices[i].getAbsolutePath();
}
for(int j=0; j < fullPathAllTopSlices.length; j++){
uriAllTopSlices[j] = Uri.parse(fullPathAllTopSlices[j]);
}
ImageView i = new ImageView(mContext);
recycleDrawable(i);
i.setImageBitmap(Utils.readBitmap(uriAllTopSlices[position].toString()));
i.setLayoutParams(new Gallery.LayoutParams(400, (screenHeightPx-50) /3));
i.setScaleType(ImageView.ScaleType.FIT_XY);
i.setBackgroundResource(mGalleryItemBackground);
System.gc();
return i;
}
}
and the referenced recycleDrawable :
private void recycleDrawable(ImageView i) {
BitmapDrawable currentBitmapDrawable = (BitmapDrawable)i.getDrawable();
if(currentBitmapDrawable != null){
currentBitmapDrawable.getBitmap().recycle();
}
System.gc();
}
and readBitmap methods:
public static Bitmap readBitmap(String selectedImage) {
Bitmap bm = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
try {
bm = BitmapFactory.decodeFile(selectedImage, options);
}
catch (OutOfMemoryError e) {
e.printStackTrace();
}
return bm;
}
Probably worth mentioning that the 'options.inSampleSize' reference, which is downsizing the image is pretty much essential when working with large files with the memory constraints associated with current Android devices.
Hope that helps someone out there.
As soon as you start to work with media files you quickly start to learn that you need to work to manage your memory usage with Android as a result of this limitation.
I would also like to thank direct to device debugging, I couldn't have done this without utilizing it, I would go as far as saying that it is essential in situations like this.
Here is the full code of one of my imageAdapters, I'm using this to provide the images to my Gallery widget like this:
galleryTop.setAdapter(new ImageAdapterTop(this));
The Adapter class :
public class ImageAdapterTop extends BaseAdapter {
int mGalleryItemBackground;
private Context mContext;
File[] allTopSlices = Utils.getAllTopSlices();
String[] fullPathAllTopSlices = new String[allTopSlices.length];
Uri[] uriAllTopSlices = new Uri[fullPathAllTopSlices.length];
public ImageAdapterTop(Context c) {
mContext = c;
TypedArray a = obtainStyledAttributes(R.styleable.HelloGallery);
mGalleryItemBackground = a.getResourceId(
R.styleable.HelloGallery_android_galleryItemBackground, 0);
a.recycle();
}
public int getCount() {
return uriAllTopSlices.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
int screenHeightPx = Utils.GetScreenHeight(getWindowManager().getDefaultDisplay());
for(int i = 0; i < allTopSlices.length; i++){
fullPathAllTopSlices[i] = allTopSlices[i].getAbsolutePath();
}
for(int j=0; j < fullPathAllTopSlices.length; j++){
uriAllTopSlices[j] = Uri.parse(fullPathAllTopSlices[j]);
}
ImageView i = new ImageView(mContext);
recycleDrawable(i);
i.setImageBitmap(Utils.readBitmap(uriAllTopSlices[position].toString()));
i.setLayoutParams(new Gallery.LayoutParams(400, (screenHeightPx-50) /3));
i.setScaleType(ImageView.ScaleType.FIT_XY);
i.setBackgroundResource(mGalleryItemBackground);
System.gc();
return i;
}
}
and the referenced recycleDrawable :
private void recycleDrawable(ImageView i) {
BitmapDrawable currentBitmapDrawable = (BitmapDrawable)i.getDrawable();
if(currentBitmapDrawable != null){
currentBitmapDrawable.getBitmap().recycle();
}
System.gc();
}
and readBitmap methods:
public static Bitmap readBitmap(String selectedImage) {
Bitmap bm = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
try {
bm = BitmapFactory.decodeFile(selectedImage, options);
}
catch (OutOfMemoryError e) {
e.printStackTrace();
}
return bm;
}
Probably worth mentioning that the 'options.inSampleSize' reference, which is downsizing the image is pretty much essential when working with large files with the memory constraints associated with current Android devices.
Hope that helps someone out there.
Sir, i still get the same error. m populating the images in a listview, may i know where m going wrong. thanks.
ReplyDelete