Reputation: 4103
I have a problem that I have 80 to 150 images of very High Resolution and I have to show them in a view pager. I had written the simple mechanism of decoding but get OutofMemory Error after the 2nd Page. I tried very much but unable to find the exact solution to avoid that. My own suggestion is that if we will able to load a image in parts on an Image View then perhaps we avoid this but I don't know how to achieve this. Please suggest any solution regarding this.
Code:
private class MyPagerAdapter extends PagerAdapter {
@Override
public int getCount() {
return Integer.parseInt(pagesCount);
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0==((ImageView)arg1);
}
@Override
public void destroyItem(View container, int position, Object object) {
View view = (View)object;
((ViewPager) container).removeView(view);
view = null;
}
@Override
public View instantiateItem(View container, final int position) {
final ImageView imgPage = new ImageView(getApplicationContext());
/*imgPage.setImageURI(Uri.withAppendedPath(
Uri.parse(Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+issueName), "" + (issueName+position)+".png"));*/
//imgPage.setImageBitmap(BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+issueName+"/"+(issueName+position)));
//destroyItem(container, position, imgPage);
imgPage.setScaleType(android.widget.ImageView.ScaleType.FIT_XY);
imgPage.setImageDrawable(getImageFromSdCard(issueName+position));
//imgPage.setImageResource(getImageFromSdCard(issueName+position));
((ViewPager) container).addView(imgPage,0);
return imgPage;
}
}
public Drawable getImageFromSdCard(String imageName) {
Drawable d = null;
try {
String path = Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+"/"+magName+issueName;
bitmap = BitmapFactory.decodeFile(path + "/" + imageName
+ ".png");
// d = new BitmapDrawable(bitmap);
d = new BitmapDrawable(decodeFile(new File(path+"/"+imageName+".png")));
// bitmap.recycle();
bitmap = null;
} catch (IllegalArgumentException e) {
bitmap = null;
}
return d;
}
//decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f){
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
//The new size we want to scale to
final int REQUIRED_SIZE=550;
//Find the correct scale value. It should be the power of 2.
int scale=1;
while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
scale*=2;
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
Thanks in advance.
Upvotes: 1
Views: 2255
Reputation: 1115
inJustDecodeBounds & inSampleSize
You are decoding high resolution images at each PagerAdapter call, and if you are willing to reduce the decoded bitmap memory by powers of 2 (x/2, x/4 ... x : original bitmap size), then go for this method
Bitmap.recycle()
A very handy method, when used at the right place, can work wonders. I am assuming that you are setting a BitmapDrawable or calling setImageBitmap to an ImageView. Add the following snippet in destroyItem() callback of PagerAdapter.
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;
ImageView imageView = (ImageView) view.findViewById(R.id.image_view);
Drawable drawable = imageView.getDrawable();
if(drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
if(bitmapDrawable != null) {
Bitmap bitmap = bitmapDrawable.getBitmap();
if(bitmap != null && !bitmap.isRecycled()) bitmap.recycle();
}
}
((ViewPager) container).removeView(view);
}
The idea is that when any child view is removed from Pager, its bitmap should get recycled, that way you would not depend on GC calls to clear memory for you.
largeHeap = true
Add this property in Manifest.xml <application>
tag. Wherever largeHeap is supported, it will increase heap memory to some large value, even goes till 8 times.
Also, in general don't hold any silly references to any bitmap, just decode them and assign it to View, rest will be taken care of.
Hope that helps. :)
Upvotes: 2
Reputation: 1502
Remove the line bitmap = BitmapFactory.decodeFile(path + "/" + imageName
+ ".png");
from your Drawable getImageFromSdCard(String imageName)
method to begin with. Also remove the bitmap variable in that method since it´s unused, now you have removed unnecessary allocations, the problem you experience could be that the GC simply does not cope with the amount of garbage / allocations that was made since you did twice as many that was needed. The method should look something like:
public Drawable getImageFromSdCard(String imageName) {
Drawable d = null;
String path = Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+"/"+magName+issueName;
d = new BitmapDrawable(decodeFile(new File(path+"/"+imageName+".png")));
return d;
}
Also reuse your o
variable in decodeFile
method just remember to flip the boolean inJustDecodeBounds
after the first call.
Also do not catch runtime exceptions, bad practice, they're there because of just runtime errors. You´re hiding the real problem by catching them.
BitmapDrawable(Bitmap bitmap)
constructor has been deprecated since api version 4, checkout BitmapDrawable for what constructor you should be using.
Upvotes: 0
Reputation: 6551
I'll suggest you to look at official android developers sample code called BitmapFun and use it for your purpose. Actually you missed o2.inPurgable=true;
Also there is no need to use o and o2, o is good enough.
//decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f){
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
//The new size we want to scale to
final int REQUIRED_SIZE=550;
//Find the correct scale value. It should be the power of 2.
int scale=1;
while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
scale*=2;
//Decode with inSampleSize
//BitmapFactory.Options o2 = new BitmapFactory.Options();
o.inSampleSize=scale;
o.inJustDecodeBounds = false;
o.inPurgealbe = true;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o);
} catch (FileNotFoundException e) {}
return null;
}
Upvotes: 0