Memory leak when loading bitmaps from path in viewholder - android

I'm trying to find out for ages now why i got this memory leak in the viewholder of my listview.
The strange part is that when setting the imageview (coverIv) with ((BitmapDrawable)_activity.Resources.GetDrawable(Resource.Drawable.splash)).Bitmap, there's no problem at all.
When i use await ImageLoader.DecodeSampledBitmapFromResourceAsync (localImageLocation, imgWidth, imgHeight) ,there's a huge memory leak each time i scroll a bit in the listview
I tried finding the references with the memory analyzer tool but there were none... Although MAT says that the problem are the bitmaps
public void ImageLoaded(string localImageLocation)
{
int screenWidth = _activity.Resources.DisplayMetrics.WidthPixels;
int imgWidth = screenWidth - (int)ConvertDpToPix (32f);
int imgHeight = (int)(ConvertDpToPix(206f));
BundleProgress.Visibility = ViewStates.Gone;
if (CoverIv.Drawable != null)
{
((BitmapDrawable)CoverIv.Drawable).Bitmap.Recycle ();
((BitmapDrawable)CoverIv.Drawable).Bitmap.Dispose ();
CoverIv.SetImageDrawable (null);
}
CoverIv.SetImageBitmap
(
//MEMORYLEAK: await ImageLoader.DecodeSampledBitmapFromResourceAsync (localImageLocation, imgWidth, imgHeight)
((BitmapDrawable)_activity.Resources.GetDrawable(Resource.Drawable.splash)).Bitmap
);
The methods in the ImageLoader class:
public static async Task<Bitmap> DecodeSampledBitmapFromResourceAsync (String path,int reqWidth, int reqHeight)
{
// First decode with inJustDecodeBounds=true to check dimensions of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
Bitmap bitmap = await BitmapFactory.DecodeFileAsync(path, options);
// Calculate inSampleSize
options.InSampleSize = CalculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
options.InPreferredConfig = Bitmap.Config.Argb8888;
//options.InDither = true;
return await BitmapFactory.DecodeFileAsync(path, options);
}
public static int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
float height = options.OutHeight;
float width = options.OutWidth;
double inSampleSize = 1D;
if (height > reqHeight || width > reqWidth)
{
int halfHeight = (int)(height / 2);
int halfWidth = (int)(width / 2);
// Calculate a inSampleSize that is a power of 2 - the decoder will use a value that is a power of two anyway.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth)
{
inSampleSize *= 2;
}
}
return (int)inSampleSize;
}
The adapter:
public override View GetView (int position, View convertView, ViewGroup parent)
{
BaseBundelVO bundle = _bundles [position];
DSBundleListItem bundleHolder = null;
DSBundleArchiveItem archiveHolder = null;
int type = GetItemViewType(position);
if (convertView == null)
{
bundleHolder = new DSBundleListItem (_activity.ApplicationContext);
archiveHolder = new DSBundleArchiveItem (_activity.ApplicationContext);
switch (type)
{
case 0:
convertView = _activity.LayoutInflater.Inflate (Resource.Layout.dsBundleListItem, null);
bundleHolder.IconIv = convertView.FindViewById<ImageView> (Resource.Id.iconIv);
bundleHolder.CoverIv = convertView.FindViewById<ImageView> (Resource.Id.coverIv);
bundleHolder.CoverTitleTv = convertView.FindViewById<TextView> (Resource.Id.coverTitleTv);
bundleHolder.CoverSubTitleTv = convertView.FindViewById<TextView> (Resource.Id.coverSubTitleTv);
bundleHolder.BundleProgress = convertView.FindViewById<ProgressBar> (Resource.Id.bundleProgress);
convertView.Tag = bundleHolder;
break;
case 1:
convertView = _activity.LayoutInflater.Inflate (Resource.Layout.dsBundleArchiveItem, null);
archiveHolder.ArchiveTitleTv = convertView.FindViewById<TextView> (Resource.Id.archiveTitleTv);
archiveHolder.ArchiveSubTitleTv = convertView.FindViewById<TextView> (Resource.Id.archiveSubTitleTv);
convertView.Tag = archiveHolder;
break;
}
}
else
{
switch (type)
{
case 0:
bundleHolder = (DSBundleListItem)convertView.Tag;
break;
case 1:
archiveHolder = (DSBundleArchiveItem)convertView.Tag;
break;
}
}
switch (type)
{
case 0:
bundleHolder.CoverTitleTv.Text = bundle.Title;
bundleHolder.CoverSubTitleTv.Text = bundle.SubTitle;
bundleHolder.CoverIv.SetImageDrawable (null);
bundleHolder.IconIv.SetImageDrawable (null);
bundleHolder.LoadImage(bundle.CoverImageLocation,bundle.Icon);
break;
case 1:
archiveHolder.ArchiveTitleTv.Text = "Archief";
archiveHolder.ArchiveSubTitleTv.Text = "Bekijk onze eerder verschenen publicaties";
break;
}
return convertView;
}
public void SetData(List<BaseBundelVO> bundles)
{
_bundles.Clear ();
_bundles.AddRange(bundles);
}

Use the picasso library when using images, it works wonders!

I recommend Pisasso library too.
Here is the link: http://square.github.io/picasso/
It retrieve images from the web immediately and caching it locally.
You must simply add this line to your code after importing Picasso library:
Picasso.with(context).load("*your url*").into(*your image view*);
You can also load local images too!
Picasso.with(context).load("file:///android_asset/image.png").into(*your image view*);
Hope this will help,

Please try my code. I think that it is something wrong with your decoding:
private Bitmap DisplayRotatedPhoto(Uri path) {
String filePath = path.toString().substring(8);
Bitmap oriented = null;
BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
bitmapFactoryOptions.inJustDecodeBounds = true;
int REQUIRED_SIZE = 100;
int width_tmp = bitmapFactoryOptions.outWidth, height_tmp = bitmapFactoryOptions.outHeight;
int scale = 2;
while (true) {
if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE)
break;
width_tmp /= 2;
height_tmp /= 2;
scale++;
}
BitmapFactory.Options bitmapFactoryOptions2 = new BitmapFactory.Options();
bitmapFactoryOptions2.inSampleSize = scale;
Bitmap bmp = BitmapFactory.decodeFile(filePath, bitmapFactoryOptions2);
}

Related

How to solve a high resolution image out of memory?

Below is the code I use to read my image file when I save my image file using FileProvider:
public static Bitmap readBitmapFromIntentReturn(Context context, Intent intentReturn, Uri uri) {
try {
AssetFileDescriptor fileDescriptor = null;
fileDescriptor = context.getContentResolver().openAssetFileDescriptor(uri, "r");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor(), null, options);
options.inSampleSize = SystemFunctions.calculateInSampleSize(options);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor(), null, options);
return bitmap;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
2. Method use to resize the image before I upload it or other, not sure the size is resize correctly or not:
public static int calculateInSampleSize(BitmapFactory.Options options) {
final int reqHeight = 360;
final int reqWidth = 360;
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
Log.d("TestSampleSize", "TestSampleSize : " + String.valueOf(inSampleSize));
return inSampleSize;
}

Bitmap null when attempting to load scaled down version

I'm attempting to scale down a bitmap to load a smaller version into memory. I'm pretty much following Google's example (search Loading Large Bitmaps Efficiently), except that I'm loading from the image gallery instead of a resource. But I seem to be getting back a null bitmap after calculating dimensions. Here's my code:
/** OnActivityResult Method **/
final Uri imageUri = data.getData();
final InputStream imageStream = getActivity().getContentResolver().openInputStream(imageUri);
Bitmap bitmapToLoad = Util.decodeSampledBitmapFromResource(imageStream, 500, 500); // bitmapToLoad is null.
mIvScreenshot.setImageBitmap(bitmapToLoad);
/**Helper Methods **/
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(InputStream is,
int reqWidth, int reqHeight) {
Rect rect = new Rect();
// First decode with inJustDecodeBounds = true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, rect, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeStream(is, rect, options);
}
Can anyone catch what I'm doing wrong?
I managed to get it working. Thanks to Biraj Zalavadia (How to reduce an Image file size before uploading to a server) for the scaling logic, and the cursor code here (How to return workable filepath?). Here is my onActivityResult():
try {
final Uri imageUri = data.getData();
String[] filePath = { MediaStore.Images.Media.DATA };
Cursor cursor = getActivity().getContentResolver().query(imageUri, filePath, null, null, null);
cursor.moveToFirst();
String imagePath = cursor.getString(cursor.getColumnIndex(filePath[0]));
Uri newUri = Uri.parse(ScalingUtilities.scaleFileAndSaveToTmp(imagePath, 500, 500));
final Bitmap selectedImage = BitmapFactory.decodeFile(newUri.getEncodedPath());
mIvScreenshot.setImageBitmap(selectedImage);
} catch (Exception e) {
// Handle
}

Xamarin Loading Bitmaps Efficiently For Gridview

So i saw this xamarin document about loading large bitmaps efficiently. Yet im struggling to implement it for the gridview.
https://developer.xamarin.com/recipes/android/resources/general/load_large_bitmaps_efficiently/
So how can we implement it for a gridview's adapter?
Thank you in the advance.
So how can we implement it for a gridview's adapter?
You can create a class(ThumbImageFactory below) to wrap all the functions mentioned in the document:
public class ThumbImageFactory
{
public readonly Context context;
public ThumbImageFactory(Context c)
{
context = c;
}
public static int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
float height = options.OutHeight;
float width = options.OutWidth;
double inSampleSize = 1D;
if (height > reqHeight || width > reqWidth)
{
int halfHeight = (int)(height / 2);
int halfWidth = (int)(width / 2);
// Calculate a inSampleSize that is a power of 2 - the decoder will use a value that is a power of two anyway.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth)
{
inSampleSize *= 2;
}
}
return (int)inSampleSize;
}
public Bitmap LoadScaledDownBitmapForDisplay(Resources res, BitmapFactory.Options options, int reqWidth, int reqHeight,int resourceId)
{
// Calculate inSampleSize
options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeResource(res, resourceId, options);
}
public BitmapFactory.Options GetBitmapOptionsOfImage(int resourceId)
{
BitmapFactory.Options options = new BitmapFactory.Options
{
InJustDecodeBounds = true
};
// The result will be null because InJustDecodeBounds == true.
Bitmap result = BitmapFactory.DecodeResource(context.Resources, resourceId, options);
int imageHeight = options.OutHeight;
int imageWidth = options.OutWidth;
return options;
}
}
Create an ThumbImageFactory instance in your Adapter and call the functions in GetView:
public class ImageAdapter : BaseAdapter
{
private readonly Context context;
private ThumbImageFactory thumbFactory;
public ImageAdapter(Context c)
{
context = c;
thumbFactory = new ThumbImageFactory(c);
}
...
public override View GetView(int position, View convertView, ViewGroup parent)
{
ImageView imageView;
if (convertView == null)
{
imageView = new ImageView(this.context);
imageView.LayoutParameters = new AbsListView.LayoutParams(150, 150);
imageView.SetScaleType(ImageView.ScaleType.CenterCrop);
imageView.SetPadding(0, 0, 0, 0);
}
else
{
imageView = (ImageView)convertView;
}
Bitmap bitmap = GetThumbImage(thumbIds[position]);
imageView.SetImageBitmap(bitmap);
return imageView;
}
public Bitmap GetThumbImage(int resourceId)
{
BitmapFactory.Options options = thumbFactory.GetBitmapOptionsOfImage(resourceId);
Bitmap bitmap=thumbFactory.LoadScaledDownBitmapForDisplay(context.Resources, options, 150, 150, resourceId);
return bitmap;
}
}
Notes: we can't modify the GetView to async, so I changed all the functions in document to sync functions. Here is is complete Demo.

BitmapFactory.decodeStream returns null if the handled image is greater than 6 mb on Android 2.2 device

in my App I need to scale an image, this works with normaly. But when a big pictured will be
loaded then the decodeStream method returns null. Currently it does not work when the image was bigger than 6 MB (5616x3744)
Does someone know why this happens and what can i do to fix this?
public static Bitmap scaleImage(final String filepath, int height, int width) throws IOException{
if (filepath == null){
Log.e(TAG, "cannot create Thumbnail without file");
return null;
}
File f = new File(filepath);
InputStream is = new FileInputStream(f);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
is.close();
final int imageHeight = options.outHeight;
final int imageWidth = options.outWidth;
String imageType = options.outMimeType;
final int thumbnailHeight = height;
final int thumbnailWidth = width;
int heightRatio = Math.round((float) imageHeight) / Math.round((float) thumbnailHeight);
int widthRatio = Math.round((float) imageWidth) / Math.round((float) thumbnailWidth);
int inSampleSize = 0;
if (heightRatio < widthRatio){
inSampleSize = heightRatio ;
}
else{
inSampleSize = widthRatio;
}
options.inSampleSize = inSampleSize;
// Decode bitmap with inSampleSize set
int retrySteps = 0;
InputStream is2 = new FileInputStream(f);
Bitmap bmp = null;
for (int i = 0; i < 5; i++){
options.inJustDecodeBounds = false;
try {
bmp = BitmapFactory.decodeStream(is2, null, options);
if (bmp != null){
i = 10;
}
else{
System.gc();
SystemClock.sleep(i * 1000);
options.inSampleSize = inSampleSize + 1;
}
} catch (OutOfMemoryError e) {
System.gc();
SystemClock.sleep(i * 1000);
}
}
is2.close();
return bmp;
thanks

MediaStore.Images.Media.getBitmap and out of memory error

My code code is:
public Bitmap loadPhoto(Uri uri) {
Bitmap scaled = null;
try {
scalled = Bitmap.createBitmap(
MediaStore.Images.Media.getBitmap(getContentResolver(), uri),
0,0,90, 90);
if (scaled == null) { return null; }
} catch(Exception e) { }
return scaled;
}
After this. I display scaled in ImageView. Every image comes from the device camera.
Every time, I get error: out of memory after I display three photos from camera. How to solve this?
Answer of Praveen Katha will always return null. Here is the updated answer.
Here is the trick, close the input stream after every use. Input Stream means to be used one time. For more information, please follow this answer
private static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromUri(Context context, Uri imageUri, int reqWidth, int reqHeight) throws FileNotFoundException {
Bitmap bitmap = null;
try {
// Get input stream of the image
final BitmapFactory.Options options = new BitmapFactory.Options();
InputStream iStream = context.getContentResolver().openInputStream(imageUri);
// First decode with inJustDecodeBounds=true to check dimensions
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(iStream, null, options);
if (iStream != null) {
iStream.close();
}
iStream = context.getContentResolver().openInputStream(imageUri);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeStream(iStream, null, options);
if (iStream != null) {
iStream.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
Try using BitmapFactory to fix the problem http://developer.android.com/reference/android/graphics/BitmapFactory.html
The MediaStore.getBitmap method is a convenience method that does not specify a sample size when obtaining the bitmap. If you are using getBitmap(ContentResolver, Uri), and want to use a sample size, just use the ContentResolver to get the input stream, and decode the bitmap as you would normally (calculating sample size first, and then loading it with the appropriate sample size).
For those who are looking for code sample:
private static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromUri(Context context, Uri imageUri, int reqWidth, int reqHeight) throws FileNotFoundException {
// Get input stream of the image
final BitmapFactory.Options options = new BitmapFactory.Options();
InputStream iStream = context.getContentResolver().openInputStream(imageUri);
// First decode with inJustDecodeBounds=true to check dimensions
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(iStream, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeStream(iStream, null, options);
}

Categories

Resources