I'm trying to make an android app with ListView that has an image in each row. I want to store all the images in SQLite database with BLOB data type and then populate the ListView with these images,but whenever I've got more than 2 jpeg images in the list i get OutOfMemoryError. I already tried many techniques found on the internet to reduce the use of memory but still can't solve this problem. I found the use of options.inJustDecodeBounds and options.inSampleSize in android documentation but still getting the same exception. Below you can find my code with Bitmap to byte array and byte array to bitmap conversion.
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class ListItem {
private int id;
private byte [] img;
private String title;
private String description;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public byte[] getImg() {
return img;
}
public void setImg(byte[] img) {
this.img = img;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public static byte[] convertBitmapToBytes(Bitmap bmp){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
return byteArray;
}
public static Bitmap getBitmap(byte [] bitmapdata){
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
ByteArrayInputStream inStream = new ByteArrayInputStream(bitmapdata);
BitmapFactory.decodeStream(inStream, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, 50, 50);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeStream(inStream,null,options);
return bmp;
}
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;
}
}
I've got the same issue in a ListView. I solved it by freeing the allocated memory on leaving the activity. For this I called the method "recycle()" for each displayd bitmap.
private List<Bitmap> m_displayedBitmaps = new ArrayList<Bitmap>();
private List<ImageView> m_displayedGridElements = new ArrayList<ImageView>();
// protected void onPause() {
public void onStop() {
// don't forget to free the references to your bitmap
for (ImageView currentGridElement : m_displayedGridElements) {
currentGridElement.setImageBitmap(null);
}
m_displayedGridElements.clear();
// recycle each image
for (Bitmap currentBitmap : m_displayedBitmaps) {
currentBitmap.recycle();
}
// clear the list
m_displayedBitmaps.clear();
super.onStop();
}
Android has a very bad image-memory managment. so you need to be very careful handling images. Fortunatelly you can try with an external library to take care of image display. Try with universal image loader UIL
You should not store images in SQLite. Instead use an image loading/caching library like Glide/Picasso, both of them are extremely simple to use and cache images out of the box.
Related
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.
Currently i'm loading images from a url and it is taking way to long and i cannot work out why, sometimes taking longer than 60 seconds to get images that aren't really that big.
My Code:
Get image async task:
public class GetImageAsyncTask extends AsyncTask<Void, Void, Bitmap> {
String url;
OnImageRetrieved listener;
ImageView imageView;
int height;
int width;
public GetImageAsyncTask(String url, ImageView imageView,OnImageRetrieved listener, int height, int width) {
this.url = url;
this.listener = listener;
this.imageView = imageView;
this.height = height;
this.width = width;
}
public interface OnImageRetrieved {
void onImageRetrieved(Bitmap image, ImageView imageview, String url);
}
protected Bitmap doInBackground(Void... params) {
Bitmap image = null;
try {
image = ImageUtilities.decodeSampledBitmapFromUrl(this.url, this.width, this.height);
} catch (Exception e) {
e.printStackTrace();
}
return image;
}
protected void onPostExecute(Bitmap result) {
this.listener.onImageRetrieved(result, this.imageView, this.url);
}
}
public static Bitmap decodeSampledBitmapFromUrl(String url, int reqWidth, int reqHeight) throws IOException {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new java.net.URL(url).openStream(), null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeStream(new java.net.URL(url).openStream(), null, options);
}
Getting sample size:
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
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;
}
}
return inSampleSize;
}
These methods are used because of memory complications that can arise if not, but the time it seems to take is just to long. Is there some very heavy computation that i'm just not seeing or?
you can use Picasso or volly library to load image.I suggest volly to use because its introduce by google itself.
So the problem stems from the array adapter and the fact that getView() can be called 100's of times which can be close to 100mb of data is being downloaded simultaneously.
So as a temp fix for this situation i implemented a global LruCache singleton, that i first check before starting the async task.
This is clearly not ideal but it will have to do for now. Im sure there are better solutions out there and i would love to hear them if anyone has one to offer.
I followed the online tutorial and created a class like so:
public class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100);
}
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public 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;
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
}
Now that I created the "ASyncTask" class, I do not know how to apply it.
What I need to do is simple. I need to draw the bitmap like so (it is set up so that it will do it every second):
canvas.drawBitmap(myBitmap, centerX, centerY, null);
How do I apply ASyncTask now so that I will draw this bitmap without doing too much work on the thread? Please help, much appreciated.
I'm learning to download images, by managing bitmaps. I used BitmapFactory:options.inJustDecodeBounds to download images.When i use this statement,
Bitmap bitmap = BitmapFactory.decodeStream(in) -it works.
But when i used sample size and tried to calculate, its not downloading. i used something like this:
public Bitmap downloadUrlToStream(String urlString, final ImageView imageView) {
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
return decodeSampledBitmapFromResource(200,200,in);
} catch (final IOException e) {
Log.e(TAG, "Error in downloadBitmap - " + e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (in != null) {
in.close();
}
} catch (final IOException e) {
}
}
return null; }
public static Bitmap decodeSampledBitmapFromResource(int reqWidth, int reqHeight, InputStream is) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeStream(is, null, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// BEGIN_INCLUDE (calculate_sample_size)
// 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;
}
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger
// inSampleSize).
long totalPixels = width * height / inSampleSize;
// Anything more than 2x the requested pixels we'll sample down
// further
final long totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels > totalReqPixelsCap) {
inSampleSize *= 2;
totalPixels /= 2;
}
}
return inSampleSize;
// END_INCLUDE (calculate_sample_size)
}
Is there a problem with calculateSamplesize() method. Y its not working?
You can able to download and resize the bitmap using Universal imageLoader, here is the sample code,
protected ImageLoader imageLoader = ImageLoader.getInstance();
DisplayImageOptions options;
options = new DisplayImageOptions.Builder().resetViewBeforeLoading()
.cacheOnDisc().imageScaleType(ImageScaleType.IN_SAMPLE_INT)
.bitmapConfig(Bitmap.Config.RGB_565)
.displayer(new FadeInBitmapDisplayer(100)).build();
imageLoader.init(ImageLoaderConfiguration.createDefault(getActivity()));
imageLoader.displayImage(
<Image_URL>,
<Image_view>, options, new ImageLoadingListener() {
#Override
public void onLoadingComplete(String imageUri,
View view, Bitmap loadedImage) {
<Image_view>.setImageBitmap(resizedBitmap(loadedImage,200,200));
}
#Override
public void onLoadingStarted(String imageUri, View view) {
// TODO Auto-generated method stub
}
#Override
public void onLoadingFailed(String imageUri, View view,
FailReason failReason) {
// TODO Auto-generated method stub
}
#Override
public void onLoadingCancelled(String imageUri,
View view) {
// TODO Auto-generated method stub
}
});
// Image resize
public Bitmap resizedBitmap(Bitmap bm, int newHeight, int newWidth) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// RECREATE THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
Note:
Don't forget to add universal image loader library.
I recommend you a different way that works like a charm: Android Query.
You can download that jar file from here: http://code.google.com/p/android-query/downloads/list
AQuery androidAQuery=new AQuery(this);
As an example:
androidAQuery.id(YOUR IMAGEVIEW).image(YOUR IMAGE TO LOAD, true, true, getDeviceWidth(), ANY DEFAULT IMAGE YOU WANT TO SHOW);
Using above code you can directly show your Image through url. Now below code is to get Bitmap Directly from the url:
androidAQuery.ajax(YOUR IMAGE URL,Bitmap.class,0,new AjaxCallback<Bitmap>(){
#Override
public void callback(String url, Bitmap object, AjaxStatus status) {
super.callback(url, object, status);
//You will get Bitmap from object.
}
});
This library is provided by Android itself, so use it and see the result whatever you want.
It's very fast and accurate, and using this you can find many more features like Animation when loading, getting a bitmap, if needed, etc.
There is no reason for it not to work. Make sure your calculateInSampleSize works properly.
options.inSampleSize need to be higher than 1. Make sure you are getting a valid value here.
Try below code:
public void DownloadImageFromPath(String path){
InputStream in =null;
Bitmap bmp=null;
ImageView iv = (ImageView)findViewById(R.id.img1);
int responseCode = -1;
try{
URL url = new URL(path);//"http://192.xx.xx.xx/mypath/img1.jpg
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setDoInput(true);
con.connect();
responseCode = con.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK)
{
//download
in = con.getInputStream();
bmp = BitmapFactory.decodeStream(in);
in.close();
iv.setImageBitmap(bmp);
}
}
catch(Exception ex){
Log.e("Exception",ex.toString());
}
}
Just get the downloaded file in bitmap and set the bitmap in imageView
URL url = new URL(urlString);
Bitmap bmp = BitmapFactory.decodeStream(url.openConnection().getInputStream());
imageView.setImageBitmap(bmp);
Hope that helps
When i was loading images form sd card i got the exception
java.lang.OutOfMemoryError at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
Here is my code:
public class Images extends Activity implements OnItemLongClickListener {
private Uri[] mUrls;
String[] mFiles = null;
ImageView selectImage;
Gallery g;
static final String MEDIA_PATH = new String("/mnt/sdcard/DCIM/Camera/");
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
selectImage = (ImageView) findViewById(R.id.image);
g = (Gallery) findViewById(R.id.gallery);
File images = new File(MEDIA_PATH);
Log.i("files", images.getAbsolutePath());
File[] imagelist = images.listFiles(new FilenameFilter() {
#Override
public boolean accept(File dir, String name) {
return ((name.endsWith(".jpg")) || (name.endsWith(".png")));
}
});
Log.i("files", imagelist.toString());
String[] mFiles = null;
mFiles = new String[imagelist.length];
for (int i = 0; i < imagelist.length; i++) {
mFiles[i] = imagelist[i].getAbsolutePath();
}
System.out.println(mFiles.length);
mUrls = new Uri[mFiles.length];
System.out.println(mUrls.length);
for (int i = 0; i < mFiles.length; i++) {
mUrls[i] = Uri.parse(mFiles[i]);
}
g.setAdapter(new ImageAdapter(this));
// g.setOnItemSelectedListener(this);
g.setOnItemLongClickListener(this);
}
public class ImageAdapter extends BaseAdapter {
// int mGalleryItemBackground;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return mUrls.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Log.i("ok5", "ok");
ImageView i = new ImageView(mContext);
i.setImageURI(mUrls[position]);
Log.i("ok", "ok");
i.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
i.setLayoutParams(new Gallery.LayoutParams(100, 100));
return i;
}
private Context mContext;
}
#Override
public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
selectImage.setImageURI(mUrls[arg2]);
System.out.println("path: "+mUrls[arg2]);
Uri f = mUrls[arg2];
File f1 = new File(f.toString());
System.out.println("f1: "+f1);
return false;
}
While you load large bitmap files, BitmapFactory class provides several decoding methods (decodeByteArray(), decodeFile(), decodeResource(), etc.).
STEP 1
Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object but setting outWidth, outHeight and outMimeType. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
To avoid java.lang.OutOfMemory exceptions, check the dimensions of a bitmap before decoding it.
STEP 2
To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize to true in your BitmapFactory.Options object.
For example, an image with resolution 2048x1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image.
Here’s a method to calculate a sample size value that is a power of two based on a target width and height:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
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;
}
Please read this link for details. http://developer.android.com/training/displaying-bitmaps/load-bitmap.html